@llblab/pi-telegram 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -16,17 +16,26 @@ import type {
16
16
  import { SettingsManager } from "@mariozechner/pi-coding-agent";
17
17
 
18
18
  import {
19
+ cleanupTelegramTempFiles,
19
20
  createTelegramApiClient,
20
21
  readTelegramConfig,
21
22
  writeTelegramConfig,
22
23
  type TelegramConfig,
23
24
  } from "./lib/api.ts";
24
25
  import { sendQueuedTelegramAttachments } from "./lib/attachments.ts";
26
+ import {
27
+ buildTelegramCommandAction,
28
+ executeTelegramCommandAction,
29
+ parseTelegramCommand,
30
+ } from "./lib/commands.ts";
25
31
  import {
26
32
  collectTelegramFileInfos,
27
33
  extractFirstTelegramMessageText,
28
34
  extractTelegramMessagesText,
29
35
  guessMediaType,
36
+ queueTelegramMediaGroupMessage,
37
+ removePendingTelegramMediaGroupMessages,
38
+ type TelegramMediaGroupState,
30
39
  } from "./lib/media.ts";
31
40
  import {
32
41
  buildTelegramModelMenuState,
@@ -52,6 +61,13 @@ import {
52
61
  shouldTriggerPendingTelegramModelSwitchAbort,
53
62
  } from "./lib/model-switch.ts";
54
63
  import { runTelegramPollLoop } from "./lib/polling.ts";
64
+ import {
65
+ clearTelegramPreview,
66
+ finalizeTelegramMarkdownPreview,
67
+ finalizeTelegramPreview,
68
+ flushTelegramPreview,
69
+ type TelegramPreviewRuntimeState,
70
+ } from "./lib/preview.ts";
55
71
  import {
56
72
  buildTelegramAgentEndPlan,
57
73
  buildTelegramAgentStartPlan,
@@ -88,10 +104,6 @@ import {
88
104
  } from "./lib/rendering.ts";
89
105
  import {
90
106
  buildTelegramReplyTransport,
91
- clearTelegramPreview,
92
- finalizeTelegramMarkdownPreview,
93
- finalizeTelegramPreview,
94
- flushTelegramPreview,
95
107
  sendTelegramMarkdownReply,
96
108
  sendTelegramPlainReply,
97
109
  } from "./lib/replies.ts";
@@ -103,159 +115,24 @@ import { buildStatusHtml } from "./lib/status.ts";
103
115
  import {
104
116
  buildTelegramPromptTurn,
105
117
  truncateTelegramQueueSummary,
118
+ updateTelegramPromptTurnText,
106
119
  } from "./lib/turns.ts";
120
+ import type {
121
+ TelegramApiResponse,
122
+ TelegramBotCommand,
123
+ TelegramCallbackQuery,
124
+ TelegramMessage,
125
+ TelegramMessageReactionUpdated,
126
+ TelegramSentMessage,
127
+ TelegramUpdate,
128
+ TelegramUser,
129
+ } from "./lib/types.ts";
107
130
  import {
108
131
  collectTelegramReactionEmojis,
109
132
  executeTelegramUpdate,
110
133
  getTelegramAuthorizationState,
111
134
  } from "./lib/updates.ts";
112
135
 
113
- // --- Telegram API Types ---
114
-
115
- interface TelegramApiResponse<T> {
116
- ok: boolean;
117
- result?: T;
118
- description?: string;
119
- error_code?: number;
120
- }
121
-
122
- interface TelegramUser {
123
- id: number;
124
- is_bot: boolean;
125
- first_name: string;
126
- username?: string;
127
- }
128
-
129
- interface TelegramChat {
130
- id: number;
131
- type: string;
132
- }
133
-
134
- interface TelegramPhotoSize {
135
- file_id: string;
136
- file_size?: number;
137
- }
138
-
139
- interface TelegramDocument {
140
- file_id: string;
141
- file_name?: string;
142
- mime_type?: string;
143
- file_size?: number;
144
- }
145
-
146
- interface TelegramVideo {
147
- file_id: string;
148
- file_name?: string;
149
- mime_type?: string;
150
- file_size?: number;
151
- }
152
-
153
- interface TelegramAudio {
154
- file_id: string;
155
- file_name?: string;
156
- mime_type?: string;
157
- file_size?: number;
158
- }
159
-
160
- interface TelegramVoice {
161
- file_id: string;
162
- mime_type?: string;
163
- file_size?: number;
164
- }
165
-
166
- interface TelegramAnimation {
167
- file_id: string;
168
- file_name?: string;
169
- mime_type?: string;
170
- file_size?: number;
171
- }
172
-
173
- interface TelegramSticker {
174
- file_id: string;
175
- emoji?: string;
176
- }
177
-
178
- interface TelegramFileInfo {
179
- file_id: string;
180
- fileName: string;
181
- mimeType?: string;
182
- isImage: boolean;
183
- }
184
-
185
- interface TelegramMessage {
186
- message_id: number;
187
- chat: TelegramChat;
188
- from?: TelegramUser;
189
- text?: string;
190
- caption?: string;
191
- media_group_id?: string;
192
- photo?: TelegramPhotoSize[];
193
- document?: TelegramDocument;
194
- video?: TelegramVideo;
195
- audio?: TelegramAudio;
196
- voice?: TelegramVoice;
197
- animation?: TelegramAnimation;
198
- sticker?: TelegramSticker;
199
- }
200
-
201
- interface TelegramCallbackQuery {
202
- id: string;
203
- from: TelegramUser;
204
- message?: TelegramMessage;
205
- data?: string;
206
- }
207
-
208
- interface TelegramReactionTypeEmoji {
209
- type: "emoji";
210
- emoji: string;
211
- }
212
-
213
- interface TelegramReactionTypeCustomEmoji {
214
- type: "custom_emoji";
215
- custom_emoji_id: string;
216
- }
217
-
218
- interface TelegramReactionTypePaid {
219
- type: "paid";
220
- }
221
-
222
- type TelegramReactionType =
223
- | TelegramReactionTypeEmoji
224
- | TelegramReactionTypeCustomEmoji
225
- | TelegramReactionTypePaid;
226
-
227
- interface TelegramMessageReactionUpdated {
228
- chat: TelegramChat;
229
- message_id: number;
230
- user?: TelegramUser;
231
- actor_chat?: TelegramChat;
232
- old_reaction: TelegramReactionType[];
233
- new_reaction: TelegramReactionType[];
234
- date: number;
235
- }
236
-
237
- interface TelegramUpdate {
238
- update_id: number;
239
- message?: TelegramMessage;
240
- edited_message?: TelegramMessage;
241
- callback_query?: TelegramCallbackQuery;
242
- message_reaction?: TelegramMessageReactionUpdated;
243
- deleted_business_messages?: { message_ids?: unknown };
244
- }
245
-
246
- interface TelegramGetFileResult {
247
- file_path: string;
248
- }
249
-
250
- interface TelegramSentMessage {
251
- message_id: number;
252
- }
253
-
254
- interface TelegramBotCommand {
255
- command: string;
256
- description: string;
257
- }
258
-
259
136
  // --- Extension State Types ---
260
137
 
261
138
  interface DownloadedTelegramFile {
@@ -267,28 +144,32 @@ interface DownloadedTelegramFile {
267
144
 
268
145
  type ActiveTelegramTurn = PendingTelegramTurn;
269
146
 
270
- interface TelegramPreviewState {
271
- mode: "draft" | "message";
272
- draftId?: number;
273
- messageId?: number;
274
- pendingText: string;
275
- lastSentText: string;
276
- flushTimer?: ReturnType<typeof setTimeout>;
147
+ type TelegramPreviewState = TelegramPreviewRuntimeState;
148
+
149
+ interface StoredTelegramModelMenuState {
150
+ state: TelegramModelMenuState;
151
+ updatedAt: number;
277
152
  }
278
153
 
279
- interface TelegramMediaGroupState {
280
- messages: TelegramMessage[];
281
- flushTimer?: ReturnType<typeof setTimeout>;
154
+ interface CachedTelegramModelMenuInputs {
155
+ expiresAt: number;
156
+ availableModels: Model<any>[];
157
+ configuredScopedModelPatterns: string[];
158
+ cliScopedModelPatterns?: string[];
282
159
  }
283
160
 
284
161
  const AGENT_DIR = join(homedir(), ".pi", "agent");
285
162
  const CONFIG_PATH = join(AGENT_DIR, "telegram.json");
286
163
  const TEMP_DIR = join(AGENT_DIR, "tmp", "telegram");
164
+ const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
287
165
  const TELEGRAM_PREFIX = "[telegram]";
288
166
  const MAX_ATTACHMENTS_PER_TURN = 10;
289
167
  const PREVIEW_THROTTLE_MS = 750;
290
168
  const TELEGRAM_DRAFT_ID_MAX = 2_147_483_647;
291
169
  const TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS = 1200;
170
+ const TELEGRAM_MODEL_MENU_CACHE_TTL_MS = 5000;
171
+ const TELEGRAM_MODEL_MENU_STATE_TTL_MS = 10 * 60 * 1000;
172
+ const MAX_STORED_TELEGRAM_MODEL_MENUS = 50;
292
173
  const SYSTEM_PROMPT_SUFFIX = `
293
174
 
294
175
  Telegram bridge extension is active.
@@ -307,17 +188,6 @@ function sanitizeFileName(name: string): string {
307
188
  return name.replace(/[^a-zA-Z0-9._-]+/g, "_");
308
189
  }
309
190
 
310
- function parseTelegramCommand(
311
- text: string,
312
- ): { name: string; args: string } | undefined {
313
- const trimmed = text.trim();
314
- if (!trimmed.startsWith("/")) return undefined;
315
- const [head, ...tail] = trimmed.split(/\s+/);
316
- const name = head.slice(1).split("@")[0]?.toLowerCase();
317
- if (!name) return undefined;
318
- return { name, args: tail.join(" ").trim() };
319
- }
320
-
321
191
  function getCliScopedModelPatterns(): string[] | undefined {
322
192
  const args = process.argv.slice(2);
323
193
  for (let i = 0; i < args.length; i++) {
@@ -400,8 +270,12 @@ export default function (pi: ExtensionAPI) {
400
270
  let draftSupport: "unknown" | "supported" | "unsupported" = "unknown";
401
271
  let nextDraftId = 0;
402
272
  let currentTelegramModel: Model<any> | undefined;
403
- const mediaGroups = new Map<string, TelegramMediaGroupState>();
404
- const modelMenus = new Map<number, TelegramModelMenuState>();
273
+ const mediaGroups = new Map<
274
+ string,
275
+ TelegramMediaGroupState<TelegramMessage>
276
+ >();
277
+ const modelMenus = new Map<number, StoredTelegramModelMenuState>();
278
+ let cachedModelMenuInputs: CachedTelegramModelMenuInputs | undefined;
405
279
 
406
280
  // --- Runtime State ---
407
281
 
@@ -691,21 +565,28 @@ export default function (pi: ExtensionAPI) {
691
565
  text,
692
566
  });
693
567
  },
694
- sendMessage: async (chatId: number, text: string) => {
568
+ sendMessage: async (
569
+ chatId: number,
570
+ text: string,
571
+ options?: { parseMode?: "HTML" },
572
+ ) => {
695
573
  return callTelegramApi<TelegramSentMessage>("sendMessage", {
696
574
  chat_id: chatId,
697
575
  text,
576
+ parse_mode: options?.parseMode,
698
577
  });
699
578
  },
700
579
  editMessageText: async (
701
580
  chatId: number,
702
581
  messageId: number,
703
582
  text: string,
583
+ options?: { parseMode?: "HTML" },
704
584
  ) => {
705
585
  await editTelegramMessageText({
706
586
  chat_id: chatId,
707
587
  message_id: messageId,
708
588
  text,
589
+ parse_mode: options?.parseMode,
709
590
  });
710
591
  },
711
592
  renderTelegramMessage,
@@ -889,24 +770,73 @@ export default function (pi: ExtensionAPI) {
889
770
 
890
771
  // --- Interactive Menu State & Builders ---
891
772
 
892
- async function getModelMenuState(
893
- chatId: number,
773
+ function pruneStoredModelMenus(now = Date.now()): void {
774
+ for (const [messageId, entry] of modelMenus.entries()) {
775
+ if (now - entry.updatedAt <= TELEGRAM_MODEL_MENU_STATE_TTL_MS) continue;
776
+ modelMenus.delete(messageId);
777
+ }
778
+ while (modelMenus.size > MAX_STORED_TELEGRAM_MODEL_MENUS) {
779
+ const oldestMessageId = modelMenus.keys().next().value as
780
+ | number
781
+ | undefined;
782
+ if (oldestMessageId === undefined) return;
783
+ modelMenus.delete(oldestMessageId);
784
+ }
785
+ }
786
+
787
+ function storeModelMenuState(state: TelegramModelMenuState): void {
788
+ pruneStoredModelMenus();
789
+ modelMenus.set(state.messageId, { state, updatedAt: Date.now() });
790
+ }
791
+
792
+ function getStoredModelMenuState(
793
+ messageId: number | undefined,
794
+ ): TelegramModelMenuState | undefined {
795
+ if (messageId === undefined) return undefined;
796
+ pruneStoredModelMenus();
797
+ const entry = modelMenus.get(messageId);
798
+ if (!entry) return undefined;
799
+ modelMenus.delete(messageId);
800
+ entry.updatedAt = Date.now();
801
+ modelMenus.set(messageId, entry);
802
+ return entry.state;
803
+ }
804
+
805
+ async function getCachedModelMenuInputs(
894
806
  ctx: ExtensionContext,
895
- ): Promise<TelegramModelMenuState> {
807
+ ): Promise<CachedTelegramModelMenuInputs> {
808
+ const now = Date.now();
809
+ if (cachedModelMenuInputs && cachedModelMenuInputs.expiresAt > now) {
810
+ return cachedModelMenuInputs;
811
+ }
896
812
  const settingsManager = SettingsManager.create(ctx.cwd);
897
813
  await settingsManager.reload();
898
814
  ctx.modelRegistry.refresh();
899
- const activeModel = getCurrentTelegramModel(ctx);
900
815
  const availableModels = ctx.modelRegistry.getAvailable();
901
- const cliScopedModels = getCliScopedModelPatterns();
902
- const configuredScopedModels =
903
- cliScopedModels ?? settingsManager.getEnabledModels() ?? [];
816
+ const cliScopedModelPatterns = getCliScopedModelPatterns();
817
+ const configuredScopedModelPatterns =
818
+ cliScopedModelPatterns ?? settingsManager.getEnabledModels() ?? [];
819
+ cachedModelMenuInputs = {
820
+ expiresAt: now + TELEGRAM_MODEL_MENU_CACHE_TTL_MS,
821
+ availableModels,
822
+ configuredScopedModelPatterns,
823
+ cliScopedModelPatterns,
824
+ };
825
+ return cachedModelMenuInputs;
826
+ }
827
+
828
+ async function getModelMenuState(
829
+ chatId: number,
830
+ ctx: ExtensionContext,
831
+ ): Promise<TelegramModelMenuState> {
832
+ const activeModel = getCurrentTelegramModel(ctx);
833
+ const inputs = await getCachedModelMenuInputs(ctx);
904
834
  return buildTelegramModelMenuState({
905
835
  chatId,
906
836
  activeModel,
907
- availableModels,
908
- configuredScopedModelPatterns: configuredScopedModels,
909
- cliScopedModelPatterns: cliScopedModels ?? undefined,
837
+ availableModels: inputs.availableModels,
838
+ configuredScopedModelPatterns: inputs.configuredScopedModelPatterns,
839
+ cliScopedModelPatterns: inputs.cliScopedModelPatterns,
910
840
  });
911
841
  }
912
842
 
@@ -1009,7 +939,7 @@ export default function (pi: ExtensionAPI) {
1009
939
  if (messageId === undefined) return;
1010
940
  state.messageId = messageId;
1011
941
  state.mode = "status";
1012
- modelMenus.set(messageId, state);
942
+ storeModelMenuState(state);
1013
943
  }
1014
944
 
1015
945
  function canOfferInFlightTelegramModelSwitch(ctx: ExtensionContext): boolean {
@@ -1147,7 +1077,7 @@ export default function (pi: ExtensionAPI) {
1147
1077
  if (messageId === undefined) return;
1148
1078
  state.messageId = messageId;
1149
1079
  state.mode = "model";
1150
- modelMenus.set(messageId, state);
1080
+ storeModelMenuState(state);
1151
1081
  }
1152
1082
 
1153
1083
  async function handleStatusCallbackAction(
@@ -1248,33 +1178,24 @@ export default function (pi: ExtensionAPI) {
1248
1178
  ctx: ExtensionContext,
1249
1179
  ): Promise<void> {
1250
1180
  const messageId = query.message?.message_id;
1251
- await handleTelegramMenuCallbackEntry(
1252
- query.id,
1253
- query.data,
1254
- messageId ? modelMenus.get(messageId) : undefined,
1255
- {
1256
- handleStatusAction: async () => {
1257
- const state = messageId ? modelMenus.get(messageId) : undefined;
1258
- if (!state) return false;
1259
- return handleStatusCallbackAction(query, state, ctx);
1260
- },
1261
- handleThinkingAction: async () => {
1262
- const state = messageId ? modelMenus.get(messageId) : undefined;
1263
- if (!state) return false;
1264
- return handleThinkingCallbackAction(query, state, ctx);
1265
- },
1266
- handleModelAction: async () => {
1267
- const state = messageId ? modelMenus.get(messageId) : undefined;
1268
- if (!state) return false;
1269
- return handleModelCallbackAction(query, state, ctx);
1270
- },
1271
- answerCallbackQuery,
1181
+ const state = getStoredModelMenuState(messageId);
1182
+ await handleTelegramMenuCallbackEntry(query.id, query.data, state, {
1183
+ handleStatusAction: async () => {
1184
+ if (!state) return false;
1185
+ return handleStatusCallbackAction(query, state, ctx);
1272
1186
  },
1273
- );
1187
+ handleThinkingAction: async () => {
1188
+ if (!state) return false;
1189
+ return handleThinkingCallbackAction(query, state, ctx);
1190
+ },
1191
+ handleModelAction: async () => {
1192
+ if (!state) return false;
1193
+ return handleModelCallbackAction(query, state, ctx);
1194
+ },
1195
+ answerCallbackQuery,
1196
+ });
1274
1197
  }
1275
1198
 
1276
- // --- Status Rendering ---
1277
-
1278
1199
  // --- Turn Queue & Message Dispatch ---
1279
1200
 
1280
1201
  async function buildTelegramFiles(
@@ -1302,19 +1223,11 @@ export default function (pi: ExtensionAPI) {
1302
1223
  }
1303
1224
 
1304
1225
  function removePendingMediaGroupMessages(messageIds: number[]): void {
1305
- if (messageIds.length === 0 || mediaGroups.size === 0) return;
1306
- const deletedMessageIds = new Set(messageIds);
1307
- for (const [key, state] of mediaGroups.entries()) {
1308
- if (
1309
- !state.messages.some((message) =>
1310
- deletedMessageIds.has(message.message_id),
1311
- )
1312
- ) {
1313
- continue;
1314
- }
1315
- if (state.flushTimer) clearTimeout(state.flushTimer);
1316
- mediaGroups.delete(key);
1317
- }
1226
+ removePendingTelegramMediaGroupMessages(
1227
+ mediaGroups,
1228
+ messageIds,
1229
+ clearTimeout,
1230
+ );
1318
1231
  }
1319
1232
 
1320
1233
  function removeQueuedTelegramTurnsByMessageIds(
@@ -1569,19 +1482,18 @@ export default function (pi: ExtensionAPI) {
1569
1482
  message: TelegramMessage,
1570
1483
  ctx: ExtensionContext,
1571
1484
  ): Promise<boolean> {
1572
- if (!commandName) return false;
1573
- const handlers: Partial<Record<string, () => Promise<void>>> = {
1574
- stop: () => handleStopCommand(message, ctx),
1575
- compact: () => handleCompactCommand(message, ctx),
1576
- status: () => handleStatusCommand(message, ctx),
1577
- model: () => handleModelCommand(message, ctx),
1578
- help: () => handleHelpCommand(message, commandName, ctx),
1579
- start: () => handleHelpCommand(message, commandName, ctx),
1580
- };
1581
- const handler = handlers[commandName];
1582
- if (!handler) return false;
1583
- await handler();
1584
- return true;
1485
+ return executeTelegramCommandAction(
1486
+ buildTelegramCommandAction(commandName),
1487
+ message,
1488
+ ctx,
1489
+ {
1490
+ handleStop: handleStopCommand,
1491
+ handleCompact: handleCompactCommand,
1492
+ handleStatus: handleStatusCommand,
1493
+ handleModel: handleModelCommand,
1494
+ handleHelp: handleHelpCommand,
1495
+ },
1496
+ );
1585
1497
  }
1586
1498
 
1587
1499
  async function enqueueTelegramTurn(
@@ -1612,25 +1524,53 @@ export default function (pi: ExtensionAPI) {
1612
1524
  await enqueueTelegramTurn(messages, ctx);
1613
1525
  }
1614
1526
 
1615
- async function handleAuthorizedTelegramMessage(
1527
+ function updateQueuedTelegramTurnFromEditedMessage(
1528
+ message: TelegramMessage,
1529
+ ctx: ExtensionContext,
1530
+ ): boolean {
1531
+ const messageText = extractTelegramMessagesText([message]);
1532
+ let changed = false;
1533
+ queuedTelegramItems = queuedTelegramItems.map((item) => {
1534
+ if (
1535
+ item.kind !== "prompt" ||
1536
+ message.message_id === undefined ||
1537
+ !item.sourceMessageIds.includes(message.message_id)
1538
+ ) {
1539
+ return item;
1540
+ }
1541
+ changed = true;
1542
+ return updateTelegramPromptTurnText({
1543
+ turn: item,
1544
+ telegramPrefix: TELEGRAM_PREFIX,
1545
+ rawText: messageText,
1546
+ });
1547
+ });
1548
+ if (changed) updateStatus(ctx);
1549
+ return changed;
1550
+ }
1551
+
1552
+ async function handleAuthorizedTelegramEditedMessage(
1616
1553
  message: TelegramMessage,
1617
1554
  ctx: ExtensionContext,
1618
1555
  ): Promise<void> {
1619
- if (message.media_group_id) {
1620
- const key = `${message.chat.id}:${message.media_group_id}`;
1621
- const existing = mediaGroups.get(key) ?? { messages: [] };
1622
- existing.messages.push(message);
1623
- if (existing.flushTimer) clearTimeout(existing.flushTimer);
1624
- existing.flushTimer = setTimeout(() => {
1625
- const state = mediaGroups.get(key);
1626
- mediaGroups.delete(key);
1627
- if (!state) return;
1628
- void dispatchAuthorizedTelegramMessages(state.messages, ctx);
1629
- }, TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS);
1630
- mediaGroups.set(key, existing);
1631
- return;
1632
- }
1556
+ updateQueuedTelegramTurnFromEditedMessage(message, ctx);
1557
+ }
1633
1558
 
1559
+ async function handleAuthorizedTelegramMessage(
1560
+ message: TelegramMessage,
1561
+ ctx: ExtensionContext,
1562
+ ): Promise<void> {
1563
+ const queuedMediaGroup = queueTelegramMediaGroupMessage({
1564
+ message,
1565
+ groups: mediaGroups,
1566
+ debounceMs: TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS,
1567
+ setTimer: setTimeout,
1568
+ clearTimer: clearTimeout,
1569
+ dispatchMessages: (messages) => {
1570
+ void dispatchAuthorizedTelegramMessages(messages, ctx);
1571
+ },
1572
+ });
1573
+ if (queuedMediaGroup) return;
1634
1574
  await dispatchAuthorizedTelegramMessages([message], ctx);
1635
1575
  }
1636
1576
 
@@ -1681,6 +1621,12 @@ export default function (pi: ExtensionAPI) {
1681
1621
  nextCtx,
1682
1622
  );
1683
1623
  },
1624
+ handleAuthorizedTelegramEditedMessage: async (message, nextCtx) => {
1625
+ await handleAuthorizedTelegramEditedMessage(
1626
+ message as TelegramMessage,
1627
+ nextCtx,
1628
+ );
1629
+ },
1684
1630
  });
1685
1631
  }
1686
1632
 
@@ -1794,6 +1740,7 @@ export default function (pi: ExtensionAPI) {
1794
1740
  sessionStartState.telegramTurnDispatchPending;
1795
1741
  compactionInProgress = sessionStartState.compactionInProgress;
1796
1742
  await mkdir(TEMP_DIR, { recursive: true });
1743
+ await cleanupTelegramTempFiles(TEMP_DIR, TELEGRAM_TEMP_FILE_MAX_AGE_MS);
1797
1744
  updateStatus(ctx);
1798
1745
  },
1799
1746
  onSessionShutdown: async (_event, _ctx) => {
@@ -1814,6 +1761,7 @@ export default function (pi: ExtensionAPI) {
1814
1761
  }
1815
1762
  mediaGroups.clear();
1816
1763
  modelMenus.clear();
1764
+ cachedModelMenuInputs = undefined;
1817
1765
  if (activeTelegramTurn) {
1818
1766
  await clearPreview(activeTelegramTurn.chatId);
1819
1767
  }