@llblab/pi-telegram 0.2.9 → 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 +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +12 -9
- package/docs/architecture.md +16 -12
- package/index.ts +191 -246
- 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 +31 -4
- package/lib/rendering.ts +105 -5
- 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/polling.test.ts +73 -0
- package/tests/preview.test.ts +39 -0
- package/tests/rendering.test.ts +51 -0
- package/tests/turns.test.ts +115 -0
- package/tests/updates.test.ts +62 -3
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,
|
|
@@ -86,13 +102,6 @@ import {
|
|
|
86
102
|
renderTelegramMessage,
|
|
87
103
|
type TelegramRenderMode,
|
|
88
104
|
} from "./lib/rendering.ts";
|
|
89
|
-
import {
|
|
90
|
-
clearTelegramPreview,
|
|
91
|
-
finalizeTelegramMarkdownPreview,
|
|
92
|
-
finalizeTelegramPreview,
|
|
93
|
-
flushTelegramPreview,
|
|
94
|
-
type TelegramPreviewRuntimeState,
|
|
95
|
-
} from "./lib/preview.ts";
|
|
96
105
|
import {
|
|
97
106
|
buildTelegramReplyTransport,
|
|
98
107
|
sendTelegramMarkdownReply,
|
|
@@ -106,159 +115,24 @@ import { buildStatusHtml } from "./lib/status.ts";
|
|
|
106
115
|
import {
|
|
107
116
|
buildTelegramPromptTurn,
|
|
108
117
|
truncateTelegramQueueSummary,
|
|
118
|
+
updateTelegramPromptTurnText,
|
|
109
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";
|
|
110
130
|
import {
|
|
111
131
|
collectTelegramReactionEmojis,
|
|
112
132
|
executeTelegramUpdate,
|
|
113
133
|
getTelegramAuthorizationState,
|
|
114
134
|
} from "./lib/updates.ts";
|
|
115
135
|
|
|
116
|
-
// --- Telegram API Types ---
|
|
117
|
-
|
|
118
|
-
interface TelegramApiResponse<T> {
|
|
119
|
-
ok: boolean;
|
|
120
|
-
result?: T;
|
|
121
|
-
description?: string;
|
|
122
|
-
error_code?: number;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
interface TelegramUser {
|
|
126
|
-
id: number;
|
|
127
|
-
is_bot: boolean;
|
|
128
|
-
first_name: string;
|
|
129
|
-
username?: string;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface TelegramChat {
|
|
133
|
-
id: number;
|
|
134
|
-
type: string;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
interface TelegramPhotoSize {
|
|
138
|
-
file_id: string;
|
|
139
|
-
file_size?: number;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
interface TelegramDocument {
|
|
143
|
-
file_id: string;
|
|
144
|
-
file_name?: string;
|
|
145
|
-
mime_type?: string;
|
|
146
|
-
file_size?: number;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
interface TelegramVideo {
|
|
150
|
-
file_id: string;
|
|
151
|
-
file_name?: string;
|
|
152
|
-
mime_type?: string;
|
|
153
|
-
file_size?: number;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
interface TelegramAudio {
|
|
157
|
-
file_id: string;
|
|
158
|
-
file_name?: string;
|
|
159
|
-
mime_type?: string;
|
|
160
|
-
file_size?: number;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
interface TelegramVoice {
|
|
164
|
-
file_id: string;
|
|
165
|
-
mime_type?: string;
|
|
166
|
-
file_size?: number;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
interface TelegramAnimation {
|
|
170
|
-
file_id: string;
|
|
171
|
-
file_name?: string;
|
|
172
|
-
mime_type?: string;
|
|
173
|
-
file_size?: number;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
interface TelegramSticker {
|
|
177
|
-
file_id: string;
|
|
178
|
-
emoji?: string;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
interface TelegramFileInfo {
|
|
182
|
-
file_id: string;
|
|
183
|
-
fileName: string;
|
|
184
|
-
mimeType?: string;
|
|
185
|
-
isImage: boolean;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
interface TelegramMessage {
|
|
189
|
-
message_id: number;
|
|
190
|
-
chat: TelegramChat;
|
|
191
|
-
from?: TelegramUser;
|
|
192
|
-
text?: string;
|
|
193
|
-
caption?: string;
|
|
194
|
-
media_group_id?: string;
|
|
195
|
-
photo?: TelegramPhotoSize[];
|
|
196
|
-
document?: TelegramDocument;
|
|
197
|
-
video?: TelegramVideo;
|
|
198
|
-
audio?: TelegramAudio;
|
|
199
|
-
voice?: TelegramVoice;
|
|
200
|
-
animation?: TelegramAnimation;
|
|
201
|
-
sticker?: TelegramSticker;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
interface TelegramCallbackQuery {
|
|
205
|
-
id: string;
|
|
206
|
-
from: TelegramUser;
|
|
207
|
-
message?: TelegramMessage;
|
|
208
|
-
data?: string;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
interface TelegramReactionTypeEmoji {
|
|
212
|
-
type: "emoji";
|
|
213
|
-
emoji: string;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface TelegramReactionTypeCustomEmoji {
|
|
217
|
-
type: "custom_emoji";
|
|
218
|
-
custom_emoji_id: string;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
interface TelegramReactionTypePaid {
|
|
222
|
-
type: "paid";
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
type TelegramReactionType =
|
|
226
|
-
| TelegramReactionTypeEmoji
|
|
227
|
-
| TelegramReactionTypeCustomEmoji
|
|
228
|
-
| TelegramReactionTypePaid;
|
|
229
|
-
|
|
230
|
-
interface TelegramMessageReactionUpdated {
|
|
231
|
-
chat: TelegramChat;
|
|
232
|
-
message_id: number;
|
|
233
|
-
user?: TelegramUser;
|
|
234
|
-
actor_chat?: TelegramChat;
|
|
235
|
-
old_reaction: TelegramReactionType[];
|
|
236
|
-
new_reaction: TelegramReactionType[];
|
|
237
|
-
date: number;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
interface TelegramUpdate {
|
|
241
|
-
update_id: number;
|
|
242
|
-
message?: TelegramMessage;
|
|
243
|
-
edited_message?: TelegramMessage;
|
|
244
|
-
callback_query?: TelegramCallbackQuery;
|
|
245
|
-
message_reaction?: TelegramMessageReactionUpdated;
|
|
246
|
-
deleted_business_messages?: { message_ids?: unknown };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
interface TelegramGetFileResult {
|
|
250
|
-
file_path: string;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
interface TelegramSentMessage {
|
|
254
|
-
message_id: number;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
interface TelegramBotCommand {
|
|
258
|
-
command: string;
|
|
259
|
-
description: string;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
136
|
// --- Extension State Types ---
|
|
263
137
|
|
|
264
138
|
interface DownloadedTelegramFile {
|
|
@@ -272,19 +146,30 @@ type ActiveTelegramTurn = PendingTelegramTurn;
|
|
|
272
146
|
|
|
273
147
|
type TelegramPreviewState = TelegramPreviewRuntimeState;
|
|
274
148
|
|
|
275
|
-
interface
|
|
276
|
-
|
|
277
|
-
|
|
149
|
+
interface StoredTelegramModelMenuState {
|
|
150
|
+
state: TelegramModelMenuState;
|
|
151
|
+
updatedAt: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface CachedTelegramModelMenuInputs {
|
|
155
|
+
expiresAt: number;
|
|
156
|
+
availableModels: Model<any>[];
|
|
157
|
+
configuredScopedModelPatterns: string[];
|
|
158
|
+
cliScopedModelPatterns?: string[];
|
|
278
159
|
}
|
|
279
160
|
|
|
280
161
|
const AGENT_DIR = join(homedir(), ".pi", "agent");
|
|
281
162
|
const CONFIG_PATH = join(AGENT_DIR, "telegram.json");
|
|
282
163
|
const TEMP_DIR = join(AGENT_DIR, "tmp", "telegram");
|
|
164
|
+
const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
283
165
|
const TELEGRAM_PREFIX = "[telegram]";
|
|
284
166
|
const MAX_ATTACHMENTS_PER_TURN = 10;
|
|
285
167
|
const PREVIEW_THROTTLE_MS = 750;
|
|
286
168
|
const TELEGRAM_DRAFT_ID_MAX = 2_147_483_647;
|
|
287
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;
|
|
288
173
|
const SYSTEM_PROMPT_SUFFIX = `
|
|
289
174
|
|
|
290
175
|
Telegram bridge extension is active.
|
|
@@ -303,17 +188,6 @@ function sanitizeFileName(name: string): string {
|
|
|
303
188
|
return name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
304
189
|
}
|
|
305
190
|
|
|
306
|
-
function parseTelegramCommand(
|
|
307
|
-
text: string,
|
|
308
|
-
): { name: string; args: string } | undefined {
|
|
309
|
-
const trimmed = text.trim();
|
|
310
|
-
if (!trimmed.startsWith("/")) return undefined;
|
|
311
|
-
const [head, ...tail] = trimmed.split(/\s+/);
|
|
312
|
-
const name = head.slice(1).split("@")[0]?.toLowerCase();
|
|
313
|
-
if (!name) return undefined;
|
|
314
|
-
return { name, args: tail.join(" ").trim() };
|
|
315
|
-
}
|
|
316
|
-
|
|
317
191
|
function getCliScopedModelPatterns(): string[] | undefined {
|
|
318
192
|
const args = process.argv.slice(2);
|
|
319
193
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -396,8 +270,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
396
270
|
let draftSupport: "unknown" | "supported" | "unsupported" = "unknown";
|
|
397
271
|
let nextDraftId = 0;
|
|
398
272
|
let currentTelegramModel: Model<any> | undefined;
|
|
399
|
-
const mediaGroups = new Map<
|
|
400
|
-
|
|
273
|
+
const mediaGroups = new Map<
|
|
274
|
+
string,
|
|
275
|
+
TelegramMediaGroupState<TelegramMessage>
|
|
276
|
+
>();
|
|
277
|
+
const modelMenus = new Map<number, StoredTelegramModelMenuState>();
|
|
278
|
+
let cachedModelMenuInputs: CachedTelegramModelMenuInputs | undefined;
|
|
401
279
|
|
|
402
280
|
// --- Runtime State ---
|
|
403
281
|
|
|
@@ -892,24 +770,73 @@ export default function (pi: ExtensionAPI) {
|
|
|
892
770
|
|
|
893
771
|
// --- Interactive Menu State & Builders ---
|
|
894
772
|
|
|
895
|
-
|
|
896
|
-
|
|
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(
|
|
897
806
|
ctx: ExtensionContext,
|
|
898
|
-
): Promise<
|
|
807
|
+
): Promise<CachedTelegramModelMenuInputs> {
|
|
808
|
+
const now = Date.now();
|
|
809
|
+
if (cachedModelMenuInputs && cachedModelMenuInputs.expiresAt > now) {
|
|
810
|
+
return cachedModelMenuInputs;
|
|
811
|
+
}
|
|
899
812
|
const settingsManager = SettingsManager.create(ctx.cwd);
|
|
900
813
|
await settingsManager.reload();
|
|
901
814
|
ctx.modelRegistry.refresh();
|
|
902
|
-
const activeModel = getCurrentTelegramModel(ctx);
|
|
903
815
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
904
|
-
const
|
|
905
|
-
const
|
|
906
|
-
|
|
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);
|
|
907
834
|
return buildTelegramModelMenuState({
|
|
908
835
|
chatId,
|
|
909
836
|
activeModel,
|
|
910
|
-
availableModels,
|
|
911
|
-
configuredScopedModelPatterns:
|
|
912
|
-
cliScopedModelPatterns:
|
|
837
|
+
availableModels: inputs.availableModels,
|
|
838
|
+
configuredScopedModelPatterns: inputs.configuredScopedModelPatterns,
|
|
839
|
+
cliScopedModelPatterns: inputs.cliScopedModelPatterns,
|
|
913
840
|
});
|
|
914
841
|
}
|
|
915
842
|
|
|
@@ -1012,7 +939,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1012
939
|
if (messageId === undefined) return;
|
|
1013
940
|
state.messageId = messageId;
|
|
1014
941
|
state.mode = "status";
|
|
1015
|
-
|
|
942
|
+
storeModelMenuState(state);
|
|
1016
943
|
}
|
|
1017
944
|
|
|
1018
945
|
function canOfferInFlightTelegramModelSwitch(ctx: ExtensionContext): boolean {
|
|
@@ -1150,7 +1077,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1150
1077
|
if (messageId === undefined) return;
|
|
1151
1078
|
state.messageId = messageId;
|
|
1152
1079
|
state.mode = "model";
|
|
1153
|
-
|
|
1080
|
+
storeModelMenuState(state);
|
|
1154
1081
|
}
|
|
1155
1082
|
|
|
1156
1083
|
async function handleStatusCallbackAction(
|
|
@@ -1251,33 +1178,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1251
1178
|
ctx: ExtensionContext,
|
|
1252
1179
|
): Promise<void> {
|
|
1253
1180
|
const messageId = query.message?.message_id;
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
handleStatusAction: async () => {
|
|
1260
|
-
const state = messageId ? modelMenus.get(messageId) : undefined;
|
|
1261
|
-
if (!state) return false;
|
|
1262
|
-
return handleStatusCallbackAction(query, state, ctx);
|
|
1263
|
-
},
|
|
1264
|
-
handleThinkingAction: async () => {
|
|
1265
|
-
const state = messageId ? modelMenus.get(messageId) : undefined;
|
|
1266
|
-
if (!state) return false;
|
|
1267
|
-
return handleThinkingCallbackAction(query, state, ctx);
|
|
1268
|
-
},
|
|
1269
|
-
handleModelAction: async () => {
|
|
1270
|
-
const state = messageId ? modelMenus.get(messageId) : undefined;
|
|
1271
|
-
if (!state) return false;
|
|
1272
|
-
return handleModelCallbackAction(query, state, ctx);
|
|
1273
|
-
},
|
|
1274
|
-
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);
|
|
1275
1186
|
},
|
|
1276
|
-
|
|
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
|
+
});
|
|
1277
1197
|
}
|
|
1278
1198
|
|
|
1279
|
-
// --- Status Rendering ---
|
|
1280
|
-
|
|
1281
1199
|
// --- Turn Queue & Message Dispatch ---
|
|
1282
1200
|
|
|
1283
1201
|
async function buildTelegramFiles(
|
|
@@ -1305,19 +1223,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1305
1223
|
}
|
|
1306
1224
|
|
|
1307
1225
|
function removePendingMediaGroupMessages(messageIds: number[]): void {
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
deletedMessageIds.has(message.message_id),
|
|
1314
|
-
)
|
|
1315
|
-
) {
|
|
1316
|
-
continue;
|
|
1317
|
-
}
|
|
1318
|
-
if (state.flushTimer) clearTimeout(state.flushTimer);
|
|
1319
|
-
mediaGroups.delete(key);
|
|
1320
|
-
}
|
|
1226
|
+
removePendingTelegramMediaGroupMessages(
|
|
1227
|
+
mediaGroups,
|
|
1228
|
+
messageIds,
|
|
1229
|
+
clearTimeout,
|
|
1230
|
+
);
|
|
1321
1231
|
}
|
|
1322
1232
|
|
|
1323
1233
|
function removeQueuedTelegramTurnsByMessageIds(
|
|
@@ -1572,19 +1482,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
1572
1482
|
message: TelegramMessage,
|
|
1573
1483
|
ctx: ExtensionContext,
|
|
1574
1484
|
): Promise<boolean> {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
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
|
+
);
|
|
1588
1497
|
}
|
|
1589
1498
|
|
|
1590
1499
|
async function enqueueTelegramTurn(
|
|
@@ -1615,25 +1524,53 @@ export default function (pi: ExtensionAPI) {
|
|
|
1615
1524
|
await enqueueTelegramTurn(messages, ctx);
|
|
1616
1525
|
}
|
|
1617
1526
|
|
|
1618
|
-
|
|
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(
|
|
1619
1553
|
message: TelegramMessage,
|
|
1620
1554
|
ctx: ExtensionContext,
|
|
1621
1555
|
): Promise<void> {
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
const existing = mediaGroups.get(key) ?? { messages: [] };
|
|
1625
|
-
existing.messages.push(message);
|
|
1626
|
-
if (existing.flushTimer) clearTimeout(existing.flushTimer);
|
|
1627
|
-
existing.flushTimer = setTimeout(() => {
|
|
1628
|
-
const state = mediaGroups.get(key);
|
|
1629
|
-
mediaGroups.delete(key);
|
|
1630
|
-
if (!state) return;
|
|
1631
|
-
void dispatchAuthorizedTelegramMessages(state.messages, ctx);
|
|
1632
|
-
}, TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS);
|
|
1633
|
-
mediaGroups.set(key, existing);
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1556
|
+
updateQueuedTelegramTurnFromEditedMessage(message, ctx);
|
|
1557
|
+
}
|
|
1636
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;
|
|
1637
1574
|
await dispatchAuthorizedTelegramMessages([message], ctx);
|
|
1638
1575
|
}
|
|
1639
1576
|
|
|
@@ -1684,6 +1621,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1684
1621
|
nextCtx,
|
|
1685
1622
|
);
|
|
1686
1623
|
},
|
|
1624
|
+
handleAuthorizedTelegramEditedMessage: async (message, nextCtx) => {
|
|
1625
|
+
await handleAuthorizedTelegramEditedMessage(
|
|
1626
|
+
message as TelegramMessage,
|
|
1627
|
+
nextCtx,
|
|
1628
|
+
);
|
|
1629
|
+
},
|
|
1687
1630
|
});
|
|
1688
1631
|
}
|
|
1689
1632
|
|
|
@@ -1797,6 +1740,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1797
1740
|
sessionStartState.telegramTurnDispatchPending;
|
|
1798
1741
|
compactionInProgress = sessionStartState.compactionInProgress;
|
|
1799
1742
|
await mkdir(TEMP_DIR, { recursive: true });
|
|
1743
|
+
await cleanupTelegramTempFiles(TEMP_DIR, TELEGRAM_TEMP_FILE_MAX_AGE_MS);
|
|
1800
1744
|
updateStatus(ctx);
|
|
1801
1745
|
},
|
|
1802
1746
|
onSessionShutdown: async (_event, _ctx) => {
|
|
@@ -1817,6 +1761,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1817
1761
|
}
|
|
1818
1762
|
mediaGroups.clear();
|
|
1819
1763
|
modelMenus.clear();
|
|
1764
|
+
cachedModelMenuInputs = undefined;
|
|
1820
1765
|
if (activeTelegramTurn) {
|
|
1821
1766
|
await clearPreview(activeTelegramTurn.chatId);
|
|
1822
1767
|
}
|