@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/AGENTS.md +2 -1
- package/CHANGELOG.md +15 -0
- package/README.md +16 -13
- package/docs/architecture.md +26 -16
- package/index.ts +199 -251
- package/lib/api.ts +277 -42
- package/lib/commands.ts +87 -0
- package/lib/media.ts +70 -1
- package/lib/polling.ts +25 -5
- package/lib/preview.ts +239 -0
- package/lib/rendering.ts +686 -49
- package/lib/replies.ts +2 -181
- package/lib/turns.ts +86 -0
- package/lib/types.ts +137 -0
- package/lib/updates.ts +64 -2
- package/package.json +1 -1
- package/tests/api.test.ts +243 -1
- package/tests/commands.test.ts +85 -0
- package/tests/media.test.ts +90 -1
- package/tests/menu.test.ts +46 -15
- package/tests/polling.test.ts +73 -0
- package/tests/preview.test.ts +480 -0
- package/tests/queue.test.ts +3 -0
- package/tests/rendering.test.ts +175 -2
- package/tests/replies.test.ts +2 -222
- package/tests/turns.test.ts +115 -0
- package/tests/updates.test.ts +72 -7
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
280
|
-
|
|
281
|
-
|
|
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<
|
|
404
|
-
|
|
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 (
|
|
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
|
-
|
|
893
|
-
|
|
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<
|
|
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
|
|
902
|
-
const
|
|
903
|
-
|
|
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:
|
|
909
|
-
cliScopedModelPatterns:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1620
|
-
|
|
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
|
}
|