@llblab/pi-telegram 0.52.2 → 0.53.1
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 +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +15 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +65 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +252 -127
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +128 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +516 -301
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/dist/lib/telegram-api.js
CHANGED
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { createWriteStream, openAsBlob } from "node:fs";
|
|
10
|
-
import { mkdir, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
10
|
+
import { mkdir, readdir, rename, stat, unlink, writeFile, } from "node:fs/promises";
|
|
11
11
|
import { request as requestHttps } from "node:https";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
-
import { resolveTelegramAttachmentsDir } from "./paths.js";
|
|
14
13
|
import { Readable, Transform } from "node:stream";
|
|
15
14
|
import { pipeline } from "node:stream/promises";
|
|
15
|
+
import { resolveTelegramAttachmentsDir } from "./paths.js";
|
|
16
16
|
const TELEGRAM_API_BASE = "https://api.telegram.org";
|
|
17
17
|
export const TELEGRAM_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
18
|
-
|
|
18
|
+
/** First positive safe-integer byte limit among ordered env names, else the caller's default. */
|
|
19
|
+
export function getTelegramByteLimitFromEnv(env, names, defaultValue) {
|
|
19
20
|
for (const name of names) {
|
|
20
21
|
const rawValue = env[name]?.trim();
|
|
21
22
|
if (!rawValue)
|
|
@@ -32,7 +33,7 @@ function getTelegramApiTempDir() {
|
|
|
32
33
|
const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
33
34
|
const TELEGRAM_DOWNLOAD_RENAME_MAX_ATTEMPTS = 6;
|
|
34
35
|
const activeTelegramApiWorkspaceAdmissionOperationIds = new Set();
|
|
35
|
-
const TELEGRAM_INBOUND_FILE_MAX_BYTES =
|
|
36
|
+
const TELEGRAM_INBOUND_FILE_MAX_BYTES = getTelegramByteLimitFromEnv(process.env, ["PI_TELEGRAM_INBOUND_FILE_MAX_BYTES", "TELEGRAM_MAX_FILE_SIZE_BYTES"], TELEGRAM_FILE_MAX_BYTES);
|
|
36
37
|
const TELEGRAM_NETWORK_FAMILY_ENV = "PI_TELEGRAM_NETWORK_FAMILY";
|
|
37
38
|
const TELEGRAM_NETWORK_FAMILY_VALUES = new Set([
|
|
38
39
|
"auto",
|
|
@@ -40,10 +41,32 @@ const TELEGRAM_NETWORK_FAMILY_VALUES = new Set([
|
|
|
40
41
|
"ipv6",
|
|
41
42
|
"ipv4-fallback",
|
|
42
43
|
]);
|
|
44
|
+
export class TelegramApiAuthorityError extends Error {
|
|
45
|
+
requestIssued;
|
|
46
|
+
constructor(requestIssued) {
|
|
47
|
+
super("Telegram API call authority is unavailable.");
|
|
48
|
+
this.name = "TelegramApiAuthorityError";
|
|
49
|
+
this.requestIssued = requestIssued;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Recheck caller authority, classifying refusal by whether a Bot API request was already issued. */
|
|
53
|
+
export function assertTelegramApiCallAuthority(assertAuthority, requestIssued) {
|
|
54
|
+
try {
|
|
55
|
+
assertAuthority?.();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new TelegramApiAuthorityError(requestIssued);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
function parseTelegramApiTargetInteger(value) {
|
|
44
|
-
const parsed = typeof value === "number"
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
const parsed = typeof value === "number"
|
|
63
|
+
? value
|
|
64
|
+
: typeof value === "string" && /^-?\d+$/u.test(value)
|
|
65
|
+
? Number(value)
|
|
66
|
+
: undefined;
|
|
67
|
+
return parsed !== undefined && Number.isSafeInteger(parsed)
|
|
68
|
+
? parsed
|
|
69
|
+
: undefined;
|
|
47
70
|
}
|
|
48
71
|
export function createTelegramApiTargetActivityRuntime() {
|
|
49
72
|
const pending = new Map();
|
|
@@ -59,8 +82,10 @@ export function createTelegramApiTargetActivityRuntime() {
|
|
|
59
82
|
const chatId = parseTelegramApiTargetInteger(body.chat_id);
|
|
60
83
|
const threadId = parseTelegramApiTargetInteger(body.message_thread_id);
|
|
61
84
|
const messageId = parseTelegramApiTargetInteger(body.message_id);
|
|
62
|
-
const chatScoped = chatId !== undefined &&
|
|
63
|
-
|
|
85
|
+
const chatScoped = chatId !== undefined &&
|
|
86
|
+
threadId === undefined &&
|
|
87
|
+
messageId !== undefined &&
|
|
88
|
+
messageScopedMethods.has(method);
|
|
64
89
|
if (chatId === undefined || (threadId === undefined && !chatScoped)) {
|
|
65
90
|
return () => undefined;
|
|
66
91
|
}
|
|
@@ -70,7 +95,10 @@ export function createTelegramApiTargetActivityRuntime() {
|
|
|
70
95
|
if (existing)
|
|
71
96
|
existing.count += 1;
|
|
72
97
|
else
|
|
73
|
-
pending.set(key, {
|
|
98
|
+
pending.set(key, {
|
|
99
|
+
target: { chatId, threadId: threadId },
|
|
100
|
+
count: 1,
|
|
101
|
+
});
|
|
74
102
|
}
|
|
75
103
|
else {
|
|
76
104
|
pendingChats.set(chatId, (pendingChats.get(chatId) ?? 0) + 1);
|
|
@@ -96,8 +124,9 @@ export function createTelegramApiTargetActivityRuntime() {
|
|
|
96
124
|
};
|
|
97
125
|
},
|
|
98
126
|
hasPendingTarget(target) {
|
|
99
|
-
return pendingChats.has(target.chatId) ||
|
|
100
|
-
(target.threadId !== undefined &&
|
|
127
|
+
return (pendingChats.has(target.chatId) ||
|
|
128
|
+
(target.threadId !== undefined &&
|
|
129
|
+
pending.has(`${target.chatId}:${target.threadId}`)));
|
|
101
130
|
},
|
|
102
131
|
listPendingTargets() {
|
|
103
132
|
return Array.from(pending.values(), ({ target }) => ({ ...target }));
|
|
@@ -216,14 +245,27 @@ function sanitizeFileName(name) {
|
|
|
216
245
|
export function createTelegramAttachmentFileName(source, generatedName, botScope) {
|
|
217
246
|
const chat = source.chat;
|
|
218
247
|
const clean = (value) => sanitizeFileName(value.replace(/^@/u, "")).replace(/^[-.]+/u, "");
|
|
219
|
-
const scope = source.scope
|
|
220
|
-
?
|
|
221
|
-
: chat
|
|
248
|
+
const scope = source.scope
|
|
249
|
+
? clean(source.scope)
|
|
250
|
+
: chat?.type === "private" || !chat
|
|
251
|
+
? botScope
|
|
252
|
+
? clean(botScope)
|
|
253
|
+
: chat
|
|
254
|
+
? String(Math.abs(chat.id))
|
|
255
|
+
: ""
|
|
256
|
+
: chat.username
|
|
257
|
+
? clean(chat.username)
|
|
258
|
+
: String(Math.abs(chat.id));
|
|
222
259
|
const dot = generatedName.lastIndexOf(".");
|
|
223
260
|
const extension = dot > 0 ? sanitizeFileName(generatedName.slice(dot)) : "";
|
|
224
|
-
const tail = source.userFileName
|
|
225
|
-
|
|
226
|
-
|
|
261
|
+
const tail = source.userFileName
|
|
262
|
+
? `-${clean(source.userFileName)}`
|
|
263
|
+
: extension;
|
|
264
|
+
return ([sanitizeFileName(source.kind), scope, String(source.messageId)]
|
|
265
|
+
.filter(Boolean)
|
|
266
|
+
.join("-") +
|
|
267
|
+
(source.index !== undefined ? `-${source.index}` : "") +
|
|
268
|
+
tail);
|
|
227
269
|
}
|
|
228
270
|
class TelegramApiMalformedSuccessError extends Error {
|
|
229
271
|
constructor(method, detail) {
|
|
@@ -288,8 +330,9 @@ export function isTelegramStaleTargetHttpError(error) {
|
|
|
288
330
|
}
|
|
289
331
|
/** Only a parsed Telegram rejection of this method proves a request had no effect. */
|
|
290
332
|
export function isTelegramApiRequestRejected(error, method) {
|
|
291
|
-
return error instanceof TelegramApiHttpError &&
|
|
292
|
-
error.rejectedRequestMethod
|
|
333
|
+
return (error instanceof TelegramApiHttpError &&
|
|
334
|
+
error.rejectedRequestMethod !== undefined &&
|
|
335
|
+
error.rejectedRequestMethod === method);
|
|
293
336
|
}
|
|
294
337
|
export function isTelegramMessageNotModifiedError(error) {
|
|
295
338
|
return (error instanceof Error && error.message.includes("message is not modified"));
|
|
@@ -322,13 +365,15 @@ export function isRetryableTelegramApiError(error) {
|
|
|
322
365
|
(error.status !== undefined && error.status >= 500)));
|
|
323
366
|
}
|
|
324
367
|
export function getTelegramApiRetryAfterMs(error) {
|
|
325
|
-
return error instanceof TelegramApiHttpError &&
|
|
368
|
+
return error instanceof TelegramApiHttpError &&
|
|
369
|
+
error.retryAfterSeconds !== undefined
|
|
326
370
|
? Math.max(0, error.retryAfterSeconds * 1000)
|
|
327
371
|
: undefined;
|
|
328
372
|
}
|
|
329
373
|
export function isTelegramMessageUnavailableError(error) {
|
|
330
|
-
return error instanceof TelegramApiHttpError &&
|
|
331
|
-
|
|
374
|
+
return (error instanceof TelegramApiHttpError &&
|
|
375
|
+
error.status === 400 &&
|
|
376
|
+
/Bad Request: (message to edit not found|message not found|message_id_invalid)/iu.test(error.message));
|
|
332
377
|
}
|
|
333
378
|
function getTelegramRetryDelayMs(error, attempt, baseDelayMs) {
|
|
334
379
|
if (error instanceof TelegramApiHttpError &&
|
|
@@ -402,7 +447,9 @@ async function publishTelegramDownload(partPath, targetPath, signal) {
|
|
|
402
447
|
catch (error) {
|
|
403
448
|
const code = error?.code;
|
|
404
449
|
// Windows may briefly deny replacement while another publisher/scanner holds the file.
|
|
405
|
-
if (process.platform !== "win32" ||
|
|
450
|
+
if (process.platform !== "win32" ||
|
|
451
|
+
(code !== "EPERM" && code !== "EACCES") ||
|
|
452
|
+
attempt + 1 >= TELEGRAM_DOWNLOAD_RENAME_MAX_ATTEMPTS)
|
|
406
453
|
throw error;
|
|
407
454
|
await sleepTelegramRetry(50 * 2 ** attempt, signal);
|
|
408
455
|
}
|
|
@@ -438,8 +485,11 @@ async function parseTelegramApiResponse(response, method) {
|
|
|
438
485
|
const retryAfterHeader = response.headers?.get("retry-after");
|
|
439
486
|
const retryAfterSeconds = data?.parameters?.retry_after ??
|
|
440
487
|
(retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : undefined);
|
|
441
|
-
throw new TelegramApiHttpError(`Telegram API ${method} failed: ${status}${description}`, response.status, Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined, data?.ok === false &&
|
|
442
|
-
|
|
488
|
+
throw new TelegramApiHttpError(`Telegram API ${method} failed: ${status}${description}`, response.status, Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined, data?.ok === false &&
|
|
489
|
+
data.error_code === response.status &&
|
|
490
|
+
[400, 401, 403, 404, 429].includes(response.status)
|
|
491
|
+
? method
|
|
492
|
+
: undefined);
|
|
443
493
|
}
|
|
444
494
|
if (!data) {
|
|
445
495
|
throw new TelegramApiMalformedSuccessError(method, "returned invalid JSON");
|
|
@@ -638,6 +688,12 @@ function withTelegramTransportDiagnostics(error, details) {
|
|
|
638
688
|
return transport ? { ...details, transport } : details;
|
|
639
689
|
}
|
|
640
690
|
async function callTelegramWithRetry(method, request, options) {
|
|
691
|
+
const assertAuthority = options?.assertAuthority;
|
|
692
|
+
let requestIssued = false;
|
|
693
|
+
const beforeRequest = () => {
|
|
694
|
+
assertTelegramApiCallAuthority(assertAuthority, requestIssued);
|
|
695
|
+
requestIssued = true;
|
|
696
|
+
};
|
|
641
697
|
const retrySafe = options?.retrySafety === "safe" ||
|
|
642
698
|
(options?.retrySafety !== "non-idempotent" &&
|
|
643
699
|
isTelegramApiMethodRetrySafe(method));
|
|
@@ -665,10 +721,17 @@ async function callTelegramWithRetry(method, request, options) {
|
|
|
665
721
|
};
|
|
666
722
|
for (let attempt = 0;; attempt += 1) {
|
|
667
723
|
throwIfTelegramApiCallAborted(options?.signal);
|
|
724
|
+
assertTelegramApiCallAuthority(assertAuthority, requestIssued);
|
|
668
725
|
try {
|
|
669
|
-
|
|
726
|
+
const result = unwrapTelegramApiResult(method, await parseTelegramApiResponse(await callTelegramTransportRequest((family) => request(family, beforeRequest), retrySafe), method));
|
|
727
|
+
assertTelegramApiCallAuthority(assertAuthority, requestIssued);
|
|
728
|
+
return result;
|
|
670
729
|
}
|
|
671
730
|
catch (error) {
|
|
731
|
+
// Authority refusal is never a transport failure, retry grant or acknowledged success.
|
|
732
|
+
if (error instanceof TelegramApiAuthorityError)
|
|
733
|
+
throw error;
|
|
734
|
+
assertTelegramApiCallAuthority(assertAuthority, requestIssued);
|
|
672
735
|
const retryable = isRetryableTelegramApiError(error) &&
|
|
673
736
|
!(options?.retryRateLimit === false &&
|
|
674
737
|
error instanceof TelegramApiHttpError &&
|
|
@@ -734,12 +797,16 @@ function assertTelegramBotTokenConfigured(botToken) {
|
|
|
734
797
|
export async function callTelegram(botToken, method, body, options) {
|
|
735
798
|
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
736
799
|
try {
|
|
737
|
-
return await callTelegramWithRetry(method, async (family) =>
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
800
|
+
return await callTelegramWithRetry(method, async (family, beforeRequest) => {
|
|
801
|
+
const encodedBody = JSON.stringify(body);
|
|
802
|
+
beforeRequest();
|
|
803
|
+
return telegramFetch(`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`, {
|
|
804
|
+
method: "POST",
|
|
805
|
+
headers: { "content-type": "application/json" },
|
|
806
|
+
body: encodedBody,
|
|
807
|
+
signal: options?.signal,
|
|
808
|
+
}, family);
|
|
809
|
+
}, options);
|
|
743
810
|
}
|
|
744
811
|
catch (error) {
|
|
745
812
|
attachTelegramApiRequestTarget(error, body);
|
|
@@ -759,11 +826,14 @@ export async function fetchTelegramBotIdentity(botToken, fetchImpl = fetch) {
|
|
|
759
826
|
*/
|
|
760
827
|
export async function callTelegramMultipart(botToken, method, fields, fileField, filePath, fileName, options) {
|
|
761
828
|
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
829
|
+
options = options && { ...options };
|
|
830
|
+
assertTelegramApiCallAuthority(options?.assertAuthority, false);
|
|
762
831
|
const fileBlob = await openAsBlob(filePath);
|
|
763
832
|
try {
|
|
764
|
-
return await callTelegramWithRetry(method, async (family) => {
|
|
833
|
+
return await callTelegramWithRetry(method, async (family, beforeRequest) => {
|
|
765
834
|
if (family) {
|
|
766
835
|
const multipart = await buildTelegramMultipartBody(fields, fileField, fileBlob, fileName);
|
|
836
|
+
beforeRequest();
|
|
767
837
|
return telegramFetch(`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`, {
|
|
768
838
|
method: "POST",
|
|
769
839
|
headers: { "content-type": multipart.contentType },
|
|
@@ -776,6 +846,7 @@ export async function callTelegramMultipart(botToken, method, fields, fileField,
|
|
|
776
846
|
form.set(key, value);
|
|
777
847
|
}
|
|
778
848
|
form.set(fileField, fileBlob, fileName);
|
|
849
|
+
beforeRequest();
|
|
779
850
|
return telegramFetch(`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`, {
|
|
780
851
|
method: "POST",
|
|
781
852
|
body: form,
|
|
@@ -812,20 +883,20 @@ export async function downloadTelegramFile(botToken, fileId, suggestedName, temp
|
|
|
812
883
|
}
|
|
813
884
|
return targetPath;
|
|
814
885
|
}
|
|
815
|
-
/** Tooltip-like callback answers omit a terminal sentence period; ellipses and other punctuation stay literal. */
|
|
816
|
-
export function formatTelegramCallbackAnswerText(text) {
|
|
817
|
-
return text?.replace(/(?<!\.)\.\s*$/u, "");
|
|
818
|
-
}
|
|
819
886
|
export async function answerTelegramCallbackQuery(botToken, callbackQueryId, text, options = {}) {
|
|
820
887
|
try {
|
|
821
888
|
await callTelegram(botToken, "answerCallbackQuery", text
|
|
822
889
|
? { callback_query_id: callbackQueryId, text }
|
|
823
|
-
: { callback_query_id: callbackQueryId }
|
|
890
|
+
: { callback_query_id: callbackQueryId }, options.assertAuthority
|
|
891
|
+
? { assertAuthority: options.assertAuthority }
|
|
892
|
+
: undefined);
|
|
824
893
|
}
|
|
825
894
|
catch (error) {
|
|
826
895
|
options.recordRuntimeEvent?.("api", error, withTelegramTransportDiagnostics(error, {
|
|
827
896
|
method: "answerCallbackQuery",
|
|
828
897
|
}));
|
|
898
|
+
if (error instanceof TelegramApiAuthorityError)
|
|
899
|
+
throw error;
|
|
829
900
|
}
|
|
830
901
|
}
|
|
831
902
|
export function createTelegramChatActionSender(sendChatAction, action) {
|
|
@@ -910,7 +981,8 @@ export function createDefaultTelegramBridgeApiRuntime(deps) {
|
|
|
910
981
|
// The exclusive deletion permit replaces ordinary target admission.
|
|
911
982
|
const target = authorize();
|
|
912
983
|
const deleted = await client.call("deleteForumTopic", {
|
|
913
|
-
chat_id: target.chatId,
|
|
984
|
+
chat_id: target.chatId,
|
|
985
|
+
message_thread_id: target.threadId,
|
|
914
986
|
}, { maxAttempts: 1, retrySafety: "non-idempotent" });
|
|
915
987
|
if (deleted !== true)
|
|
916
988
|
throw new Error("Telegram Workspace Thread deletion was not confirmed.");
|
|
@@ -923,7 +995,9 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
923
995
|
await handler?.(error);
|
|
924
996
|
}
|
|
925
997
|
catch (recoveryError) {
|
|
926
|
-
deps.recordRuntimeEvent("api", recoveryError, {
|
|
998
|
+
deps.recordRuntimeEvent("api", recoveryError, {
|
|
999
|
+
phase: "stale-target-recovery",
|
|
1000
|
+
});
|
|
927
1001
|
}
|
|
928
1002
|
};
|
|
929
1003
|
const now = deps.now ?? Date.now;
|
|
@@ -1028,9 +1102,11 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
1028
1102
|
}
|
|
1029
1103
|
catch (error) {
|
|
1030
1104
|
await recoverRequestError(recoverError, error);
|
|
1031
|
-
if (method === "deleteMessage" &&
|
|
1032
|
-
error
|
|
1033
|
-
|
|
1105
|
+
if (method === "deleteMessage" &&
|
|
1106
|
+
error instanceof TelegramApiHttpError &&
|
|
1107
|
+
error.status === 400 &&
|
|
1108
|
+
error.message ===
|
|
1109
|
+
"Telegram API deleteMessage failed: HTTP 400: Bad Request: message to delete not found") {
|
|
1034
1110
|
return true;
|
|
1035
1111
|
}
|
|
1036
1112
|
deps.recordRuntimeEvent("api", error, withTelegramTransportDiagnostics(error, { method }));
|
|
@@ -1074,7 +1150,7 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
1074
1150
|
},
|
|
1075
1151
|
deleteWebhook: (signal) => callRecorded("deleteWebhook", { drop_pending_updates: false }, { signal }),
|
|
1076
1152
|
getUpdates: (body, signal) => callRecorded("getUpdates", body, { signal }),
|
|
1077
|
-
setMyCommands: (commands) => callRecorded("setMyCommands", { commands }),
|
|
1153
|
+
setMyCommands: (commands, options) => callRecorded("setMyCommands", { commands }, options),
|
|
1078
1154
|
sendChatAction: (chatId, action, options) => callRecorded("sendChatAction", {
|
|
1079
1155
|
chat_id: chatId,
|
|
1080
1156
|
action,
|
|
@@ -1111,13 +1187,13 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
1111
1187
|
body.message_thread_id = options.message_thread_id;
|
|
1112
1188
|
return callRecorded("sendMessageDraft", body);
|
|
1113
1189
|
},
|
|
1114
|
-
sendMessage: (body) => callRecorded("sendMessage", body),
|
|
1115
|
-
sendRichMessage: (body) => callRecorded("sendRichMessage", body),
|
|
1190
|
+
sendMessage: (body, options) => callRecorded("sendMessage", body, options),
|
|
1191
|
+
sendRichMessage: (body, options) => callRecorded("sendRichMessage", body, options),
|
|
1116
1192
|
sendRichMessageDraft: (body) => callRecorded("sendRichMessageDraft", body),
|
|
1117
|
-
editMessageText: async (body) => {
|
|
1193
|
+
editMessageText: async (body, options) => {
|
|
1118
1194
|
const recoverError = deps.captureRequestErrorHandler?.(body);
|
|
1119
1195
|
try {
|
|
1120
|
-
await deps.client.call("editMessageText", body);
|
|
1196
|
+
await deps.client.call("editMessageText", body, options);
|
|
1121
1197
|
return "edited";
|
|
1122
1198
|
}
|
|
1123
1199
|
catch (error) {
|
|
@@ -1137,7 +1213,19 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
1137
1213
|
reply_markup: replyMarkup,
|
|
1138
1214
|
});
|
|
1139
1215
|
},
|
|
1140
|
-
answerCallbackQuery: async (callbackQueryId, text) => {
|
|
1216
|
+
answerCallbackQuery: async (callbackQueryId, text, options) => {
|
|
1217
|
+
if (options?.assertAuthority) {
|
|
1218
|
+
try {
|
|
1219
|
+
await callRecorded("answerCallbackQuery", text
|
|
1220
|
+
? { callback_query_id: callbackQueryId, text }
|
|
1221
|
+
: { callback_query_id: callbackQueryId }, { assertAuthority: options.assertAuthority });
|
|
1222
|
+
}
|
|
1223
|
+
catch (error) {
|
|
1224
|
+
if (error instanceof TelegramApiAuthorityError)
|
|
1225
|
+
throw error;
|
|
1226
|
+
}
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1141
1229
|
try {
|
|
1142
1230
|
await deps.client.answerCallbackQuery(callbackQueryId, text);
|
|
1143
1231
|
}
|
|
@@ -1145,6 +1233,8 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
1145
1233
|
deps.recordRuntimeEvent("api", error, withTelegramTransportDiagnostics(error, {
|
|
1146
1234
|
method: "answerCallbackQuery",
|
|
1147
1235
|
}));
|
|
1236
|
+
if (error instanceof TelegramApiAuthorityError)
|
|
1237
|
+
throw error;
|
|
1148
1238
|
}
|
|
1149
1239
|
},
|
|
1150
1240
|
answerGuestQuery: (guestQueryId, text, options) => callRecorded("answerGuestQuery", buildTelegramAnswerGuestQueryBody(guestQueryId, text, options)),
|
|
@@ -1219,7 +1309,8 @@ export function createTelegramApiClient(getBotToken, options = {}) {
|
|
|
1219
1309
|
}
|
|
1220
1310
|
catch (error) {
|
|
1221
1311
|
if (draftKey && isRetryableTelegramApiError(error)) {
|
|
1222
|
-
draftRetryNotBeforeByTarget.set(draftKey, Math.max(draftRetryNotBeforeByTarget.get(draftKey) ?? 0, now() +
|
|
1312
|
+
draftRetryNotBeforeByTarget.set(draftKey, Math.max(draftRetryNotBeforeByTarget.get(draftKey) ?? 0, now() +
|
|
1313
|
+
getTelegramRetryDelayMs(error, 0, options?.retryBaseDelayMs ?? 500)));
|
|
1223
1314
|
}
|
|
1224
1315
|
throw error;
|
|
1225
1316
|
}
|
|
@@ -1230,8 +1321,12 @@ export function createTelegramApiClient(getBotToken, options = {}) {
|
|
|
1230
1321
|
downloadFile: async (fileId, suggestedName, tempDir, options) => {
|
|
1231
1322
|
return downloadTelegramFile(getBotToken(), fileId, suggestedName, tempDir, options);
|
|
1232
1323
|
},
|
|
1233
|
-
answerCallbackQuery: async (callbackQueryId, text) => {
|
|
1234
|
-
|
|
1324
|
+
answerCallbackQuery: async (callbackQueryId, text, authorityOptions) => {
|
|
1325
|
+
const assertAuthority = authorityOptions?.assertAuthority;
|
|
1326
|
+
const callbackOptions = assertAuthority
|
|
1327
|
+
? { ...options, assertAuthority }
|
|
1328
|
+
: options;
|
|
1329
|
+
await answerTelegramCallbackQuery(getBotToken(), callbackQueryId, text, callbackOptions);
|
|
1235
1330
|
},
|
|
1236
1331
|
};
|
|
1237
1332
|
}
|
package/dist/lib/text-groups.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns conservative delayed grouping for Telegram text messages that look like automatic long-message splits
|
|
5
5
|
*/
|
|
6
6
|
import { setTimeout as waitForTimeout } from "node:timers/promises";
|
|
7
|
-
import { extractTelegramMessageText, } from "./media.js";
|
|
7
|
+
import { createTelegramPendingGroupLifecycle, extractTelegramMessageText, } from "./media.js";
|
|
8
8
|
const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS = 1000;
|
|
9
9
|
const TELEGRAM_TEXT_GROUP_MIN_SPLIT_LENGTH = 3600;
|
|
10
10
|
const TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP = 12;
|
|
@@ -155,6 +155,7 @@ export function createTelegramTextGroupController(options = {}) {
|
|
|
155
155
|
: (timer) => {
|
|
156
156
|
timer.abort();
|
|
157
157
|
});
|
|
158
|
+
const lifecycle = createTelegramPendingGroupLifecycle(groups, clearTimer);
|
|
158
159
|
return {
|
|
159
160
|
prepareUpdateBatch(updates) {
|
|
160
161
|
for (let index = 0; index + 1 < updates.length; index += 1) {
|
|
@@ -249,45 +250,9 @@ export function createTelegramTextGroupController(options = {}) {
|
|
|
249
250
|
: undefined,
|
|
250
251
|
});
|
|
251
252
|
},
|
|
252
|
-
|
|
253
|
-
for (const state of groups.values()) {
|
|
254
|
-
if (!state.messages.some((message) => message.message_id === messageId)) {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
if (state.flushTimer)
|
|
258
|
-
clearTimer(state.flushTimer);
|
|
259
|
-
state.flushTimer = undefined;
|
|
260
|
-
await state.dispatchNow?.();
|
|
261
|
-
if (state.messages.some((message) => message.message_id === messageId) &&
|
|
262
|
-
!state.dispatching) {
|
|
263
|
-
await state.dispatchNow?.();
|
|
264
|
-
}
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
return false;
|
|
268
|
-
},
|
|
269
|
-
suspend: () => {
|
|
270
|
-
for (const state of groups.values()) {
|
|
271
|
-
state.suspended = true;
|
|
272
|
-
if (state.flushTimer)
|
|
273
|
-
clearTimer(state.flushTimer);
|
|
274
|
-
state.flushTimer = undefined;
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
resume: (context) => {
|
|
278
|
-
for (const state of groups.values()) {
|
|
279
|
-
state.context = context;
|
|
280
|
-
state.suspended = false;
|
|
281
|
-
if (!state.dispatching && !state.flushTimer)
|
|
282
|
-
state.reschedule?.();
|
|
283
|
-
}
|
|
284
|
-
},
|
|
253
|
+
...lifecycle,
|
|
285
254
|
clear: () => {
|
|
286
|
-
|
|
287
|
-
if (state.flushTimer)
|
|
288
|
-
clearTimer(state.flushTimer);
|
|
289
|
-
}
|
|
290
|
-
groups.clear();
|
|
255
|
+
lifecycle.clear();
|
|
291
256
|
plannedForwardCommentStarts.clear();
|
|
292
257
|
plannedForwardCommentEnds.clear();
|
|
293
258
|
},
|
|
@@ -64,8 +64,8 @@ export declare function planTelegramInactiveThreadCleanup(input: {
|
|
|
64
64
|
threadId: number;
|
|
65
65
|
}[];
|
|
66
66
|
}): TelegramThreadCleanupCandidate[];
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
/** Read ports for the bindings and target claims that cleanup evidence is captured from. */
|
|
68
|
+
export interface TelegramInactiveThreadCleanupEvidencePorts<TBinding extends TelegramThreadCleanupBindingSnapshot> {
|
|
69
69
|
listBindings(): readonly TBinding[];
|
|
70
70
|
getProtection(binding: TBinding): {
|
|
71
71
|
liveOwner: TelegramThreadCleanupProtectionState;
|
|
@@ -90,33 +90,12 @@ export declare function captureTelegramInactiveThreadCleanupEvidence<TBinding ex
|
|
|
90
90
|
threadId: number;
|
|
91
91
|
};
|
|
92
92
|
}[];
|
|
93
|
+
}
|
|
94
|
+
export declare function captureTelegramInactiveThreadCleanupEvidence<TBinding extends TelegramThreadCleanupBindingSnapshot>(input: TelegramInactiveThreadCleanupEvidencePorts<TBinding> & {
|
|
95
|
+
profileName: string;
|
|
93
96
|
}): Parameters<typeof planTelegramInactiveThreadCleanup>[0];
|
|
94
|
-
export declare function createTelegramInactiveThreadCleanupReviewRuntime<TBinding extends TelegramThreadCleanupBindingSnapshot>(deps: {
|
|
97
|
+
export declare function createTelegramInactiveThreadCleanupReviewRuntime<TBinding extends TelegramThreadCleanupBindingSnapshot>(deps: TelegramInactiveThreadCleanupEvidencePorts<TBinding> & {
|
|
95
98
|
getProfileName(): string;
|
|
96
|
-
listBindings(): readonly TBinding[];
|
|
97
|
-
getProtection(binding: TBinding): {
|
|
98
|
-
liveOwner: TelegramThreadCleanupProtectionState;
|
|
99
|
-
acceptedWork: TelegramThreadCleanupProtectionState;
|
|
100
|
-
deliveryAuthority: TelegramThreadCleanupProtectionState;
|
|
101
|
-
};
|
|
102
|
-
listReservations(): readonly {
|
|
103
|
-
target: {
|
|
104
|
-
chatId: number;
|
|
105
|
-
threadId: number;
|
|
106
|
-
};
|
|
107
|
-
}[];
|
|
108
|
-
listPendingProvisions(): readonly {
|
|
109
|
-
target?: {
|
|
110
|
-
chatId: number;
|
|
111
|
-
threadId: number;
|
|
112
|
-
};
|
|
113
|
-
}[];
|
|
114
|
-
listPendingCleanups(): readonly {
|
|
115
|
-
target: {
|
|
116
|
-
chatId: number;
|
|
117
|
-
threadId: number;
|
|
118
|
-
};
|
|
119
|
-
}[];
|
|
120
99
|
getWorkStore(): TelegramThreadCleanupWorkStore;
|
|
121
100
|
runWorkspaceOperation<T>(input: {
|
|
122
101
|
operationId: string;
|