@llblab/pi-telegram 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +0 -1
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/dist/api/activity.d.ts +6 -0
- package/dist/api/activity.js +6 -0
- package/dist/api/commands.d.ts +6 -0
- package/dist/api/commands.js +6 -0
- package/dist/api/delivery.d.ts +6 -0
- package/dist/api/delivery.js +6 -0
- package/dist/api/inbound.d.ts +6 -0
- package/dist/api/inbound.js +6 -0
- package/dist/api/keyboard.d.ts +6 -0
- package/dist/api/keyboard.js +6 -0
- package/dist/api/outbound.d.ts +6 -0
- package/dist/api/outbound.js +6 -0
- package/dist/api/sections.d.ts +7 -0
- package/dist/api/sections.js +6 -0
- package/dist/api/status.d.ts +6 -0
- package/dist/api/status.js +6 -0
- package/dist/api/updates.d.ts +6 -0
- package/dist/api/updates.js +6 -0
- package/dist/api/voice.d.ts +6 -0
- package/dist/api/voice.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/activity-verbosity.d.ts +52 -0
- package/dist/lib/activity-verbosity.js +596 -0
- package/dist/lib/activity.d.ts +220 -0
- package/dist/lib/activity.js +574 -0
- package/dist/lib/agent-messages.d.ts +28 -0
- package/dist/lib/agent-messages.js +86 -0
- package/dist/lib/bindings.d.ts +250 -0
- package/dist/lib/bindings.js +936 -0
- package/dist/lib/bus-api.d.ts +18 -0
- package/dist/lib/bus-api.js +254 -0
- package/dist/lib/bus-follower.d.ts +464 -0
- package/dist/lib/bus-follower.js +1686 -0
- package/dist/lib/bus-leader.d.ts +292 -0
- package/dist/lib/bus-leader.js +2242 -0
- package/dist/lib/bus-transport.d.ts +64 -0
- package/dist/lib/bus-transport.js +140 -0
- package/dist/lib/bus.d.ts +518 -0
- package/dist/lib/bus.js +2055 -0
- package/dist/lib/channel-posts.d.ts +170 -0
- package/dist/lib/channel-posts.js +611 -0
- package/dist/lib/command-templates.d.ts +70 -0
- package/dist/lib/command-templates.js +744 -0
- package/dist/lib/commands.d.ts +541 -0
- package/dist/lib/commands.js +1155 -0
- package/dist/lib/config.d.ts +252 -0
- package/dist/lib/config.js +889 -0
- package/dist/lib/delivery.d.ts +163 -0
- package/dist/lib/delivery.js +542 -0
- package/dist/lib/extension.d.ts +7 -0
- package/dist/lib/extension.js +1608 -0
- package/dist/lib/generative-app-worker.mjs +104 -0
- package/dist/lib/generative-apps.d.ts +212 -0
- package/dist/lib/generative-apps.js +1006 -0
- package/dist/lib/inbound.d.ts +91 -0
- package/dist/lib/inbound.js +502 -0
- package/dist/lib/journal.d.ts +665 -0
- package/dist/lib/journal.js +3701 -0
- package/dist/lib/keyboard.d.ts +23 -0
- package/dist/lib/keyboard.js +37 -0
- package/dist/lib/lifecycle.d.ts +157 -0
- package/dist/lib/lifecycle.js +395 -0
- package/dist/lib/locks.d.ts +164 -0
- package/dist/lib/locks.js +1208 -0
- package/dist/lib/logging.d.ts +50 -0
- package/dist/lib/logging.js +274 -0
- package/dist/lib/media.d.ts +181 -0
- package/dist/lib/media.js +602 -0
- package/dist/lib/menu-model.d.ts +217 -0
- package/dist/lib/menu-model.js +663 -0
- package/dist/lib/menu-queue.d.ts +37 -0
- package/dist/lib/menu-queue.js +408 -0
- package/dist/lib/menu-settings.d.ts +116 -0
- package/dist/lib/menu-settings.js +595 -0
- package/dist/lib/menu-status.d.ts +32 -0
- package/dist/lib/menu-status.js +112 -0
- package/dist/lib/menu-thinking.d.ts +29 -0
- package/dist/lib/menu-thinking.js +82 -0
- package/dist/lib/menu.d.ts +171 -0
- package/dist/lib/menu.js +323 -0
- package/dist/lib/model.d.ts +127 -0
- package/dist/lib/model.js +409 -0
- package/dist/lib/outbound-attachments.d.ts +241 -0
- package/dist/lib/outbound-attachments.js +639 -0
- package/dist/lib/outbound-buttons.d.ts +69 -0
- package/dist/lib/outbound-buttons.js +248 -0
- package/dist/lib/outbound-markup.d.ts +42 -0
- package/dist/lib/outbound-markup.js +678 -0
- package/dist/lib/outbound-voice.d.ts +55 -0
- package/dist/lib/outbound-voice.js +152 -0
- package/dist/lib/outbound.d.ts +185 -0
- package/dist/lib/outbound.js +516 -0
- package/dist/lib/ownership.d.ts +77 -0
- package/dist/lib/ownership.js +174 -0
- package/dist/lib/paths.d.ts +40 -0
- package/dist/lib/paths.js +94 -0
- package/dist/lib/pi.d.ts +72 -0
- package/dist/lib/pi.js +121 -0
- package/dist/lib/polling.d.ts +351 -0
- package/dist/lib/polling.js +1196 -0
- package/dist/lib/preview.d.ts +192 -0
- package/dist/lib/preview.js +614 -0
- package/dist/lib/prompt-templates.d.ts +24 -0
- package/dist/lib/prompt-templates.js +118 -0
- package/dist/lib/prompts.d.ts +57 -0
- package/dist/lib/prompts.js +167 -0
- package/dist/lib/queue.d.ts +766 -0
- package/dist/lib/queue.js +1965 -0
- package/dist/lib/recovery.d.ts +86 -0
- package/dist/lib/recovery.js +285 -0
- package/dist/lib/rendering.d.ts +20 -0
- package/dist/lib/rendering.js +983 -0
- package/dist/lib/replies.d.ts +214 -0
- package/dist/lib/replies.js +737 -0
- package/dist/lib/routing.d.ts +207 -0
- package/dist/lib/routing.js +2282 -0
- package/dist/lib/runtime.d.ts +172 -0
- package/dist/lib/runtime.js +402 -0
- package/dist/lib/sections.d.ts +165 -0
- package/dist/lib/sections.js +327 -0
- package/dist/lib/setup.d.ts +82 -0
- package/dist/lib/setup.js +150 -0
- package/dist/lib/skills.d.ts +8 -0
- package/dist/lib/skills.js +12 -0
- package/dist/lib/status.d.ts +442 -0
- package/dist/lib/status.js +1006 -0
- package/dist/lib/sync.d.ts +179 -0
- package/dist/lib/sync.js +782 -0
- package/dist/lib/target.d.ts +20 -0
- package/dist/lib/target.js +27 -0
- package/dist/lib/telegram-api.d.ts +541 -0
- package/dist/lib/telegram-api.js +1159 -0
- package/dist/lib/text-groups.d.ts +89 -0
- package/dist/lib/text-groups.js +317 -0
- package/dist/lib/thread-cleanup-manager.d.ts +288 -0
- package/dist/lib/thread-cleanup-manager.js +560 -0
- package/dist/lib/thread-display.d.ts +46 -0
- package/dist/lib/thread-display.js +257 -0
- package/dist/lib/thread-naming.d.ts +46 -0
- package/dist/lib/thread-naming.js +78 -0
- package/dist/lib/thread-reconciler.d.ts +239 -0
- package/dist/lib/thread-reconciler.js +644 -0
- package/dist/lib/threads.d.ts +621 -0
- package/dist/lib/threads.js +3679 -0
- package/dist/lib/time-injection.d.ts +15 -0
- package/dist/lib/time-injection.js +56 -0
- package/dist/lib/turns.d.ts +109 -0
- package/dist/lib/turns.js +500 -0
- package/dist/lib/updates.d.ts +1290 -0
- package/dist/lib/updates.js +3634 -0
- package/dist/lib/voice.d.ts +117 -0
- package/dist/lib/voice.js +174 -0
- package/dist/lib/workspace-admission.d.ts +264 -0
- package/dist/lib/workspace-admission.js +1136 -0
- package/dist/lib/workspace-retirement.d.ts +219 -0
- package/dist/lib/workspace-retirement.js +587 -0
- package/dist/lib/workspace-slots.d.ts +30 -0
- package/dist/lib/workspace-slots.js +54 -0
- package/dist/package.json +126 -0
- package/dist/pi-telegram/index.js +1 -0
- package/dist/skills/generated-control-surface/SKILL.md +107 -0
- package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
- package/dist/skills/generative-apps/SKILL.md +115 -0
- package/dist/skills/show-me/SKILL.md +166 -0
- package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
- package/dist/skills/telegram-bridge/SKILL.md +129 -0
- package/dist/skills/telegram-bridge/references/configuration.md +15 -0
- package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
- package/docs/architecture.md +1 -1
- package/docs/compact-matrix-literal.md +10 -3
- package/docs/generative-apps.md +15 -13
- package/docs/multi-instance-bus.md +4 -2
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +2 -2
- package/lib/bindings.ts +89 -6
- package/lib/bus-follower.ts +7 -3
- package/lib/bus-leader.ts +44 -19
- package/lib/bus.ts +4 -1
- package/lib/commands.ts +2 -2
- package/lib/config.ts +14 -7
- package/lib/delivery.ts +38 -6
- package/lib/extension.ts +19 -0
- package/lib/generative-apps.ts +379 -13
- package/lib/menu-settings.ts +21 -5
- package/lib/outbound-markup.ts +16 -7
- package/lib/telegram-api.ts +12 -1
- package/lib/thread-display.ts +96 -18
- package/package.json +56 -13
- package/scripts/build-dist.mjs +44 -0
- package/scripts/measure-bus.mjs +8 -1
- package/scripts/measure-workspace.mjs +8 -1
- package/skills/generative-apps/SKILL.md +1 -1
- package/skills/telegram-bridge/SKILL.md +1 -1
package/lib/generative-apps.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns Generative App identity, installation, invocation, state history, and telegram_bind
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import {
|
|
9
9
|
appendFile,
|
|
10
10
|
copyFile,
|
|
@@ -33,6 +33,8 @@ const GENERATIVE_APP_LOCK_WAIT_MS = 12_000;
|
|
|
33
33
|
const GENERATIVE_APP_RUN_MAX_TIMEOUT_MS = 30_000;
|
|
34
34
|
const GENERATIVE_APP_RUN_MAX_ARGS = 64;
|
|
35
35
|
const GENERATIVE_APP_RUN_MAX_STREAM_BYTES = 64 * 1024;
|
|
36
|
+
export const GENERATIVE_APP_MIN_REFRESH_AFTER_MS = 2_000;
|
|
37
|
+
export const GENERATIVE_APP_MAX_REFRESH_AFTER_MS = 24 * 60 * 60 * 1_000;
|
|
36
38
|
|
|
37
39
|
export type GenerativeAppJsonValue =
|
|
38
40
|
| null
|
|
@@ -66,6 +68,7 @@ export interface GenerativeAppMethodContext {
|
|
|
66
68
|
|
|
67
69
|
export interface GenerativeAppMethodResult {
|
|
68
70
|
output: string;
|
|
71
|
+
refreshAfterMs?: number;
|
|
69
72
|
state?: GenerativeAppJsonValue;
|
|
70
73
|
viewMode?: "new" | "edit";
|
|
71
74
|
}
|
|
@@ -82,6 +85,7 @@ export interface GenerativeAppInvocationResult {
|
|
|
82
85
|
output: string;
|
|
83
86
|
app: string;
|
|
84
87
|
revision: number;
|
|
88
|
+
refreshAfterMs?: number;
|
|
85
89
|
stateChanged: boolean;
|
|
86
90
|
viewMode: "new" | "edit";
|
|
87
91
|
}
|
|
@@ -104,7 +108,89 @@ export interface GenerativeAppRuntimeOptions {
|
|
|
104
108
|
methodTimeoutMs?: number;
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
export interface GenerativeAppLiveSurfaceFrame<THandle> {
|
|
112
|
+
digest: string;
|
|
113
|
+
handle: THandle;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface GenerativeAppLiveSurface<THandle> {
|
|
117
|
+
app: string;
|
|
118
|
+
appGeneration: string;
|
|
119
|
+
appRevision: number;
|
|
120
|
+
handle: THandle;
|
|
121
|
+
initialDigest: string;
|
|
122
|
+
key: string;
|
|
123
|
+
refreshAfterMs: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface GenerativeAppLiveSurfaceRuntime<THandle> {
|
|
127
|
+
open: (surface: GenerativeAppLiveSurface<THandle>) => void;
|
|
128
|
+
cancel: (key: string) => void;
|
|
129
|
+
take: (key: string) => GenerativeAppLiveSurface<THandle> | undefined;
|
|
130
|
+
resume: (
|
|
131
|
+
surface: GenerativeAppLiveSurface<THandle>,
|
|
132
|
+
result: GenerativeAppInvocationResult,
|
|
133
|
+
) => Promise<void>;
|
|
134
|
+
refreshNow: (key: string) => Promise<void>;
|
|
135
|
+
shutdown: () => void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface GenerativeAppLiveSurfaceRuntimeDeps<THandle> {
|
|
139
|
+
agentDir: string;
|
|
140
|
+
isCurrent: (surface: GenerativeAppLiveSurface<THandle>) => boolean;
|
|
141
|
+
plan: (
|
|
142
|
+
result: GenerativeAppInvocationResult,
|
|
143
|
+
handle: THandle,
|
|
144
|
+
) => GenerativeAppLiveSurfaceFrame<THandle>;
|
|
145
|
+
edit: (
|
|
146
|
+
frame: GenerativeAppLiveSurfaceFrame<THandle>,
|
|
147
|
+
) => Promise<THandle>;
|
|
148
|
+
classifyEditError?: (error: unknown) =>
|
|
149
|
+
| { kind: "retry"; retryAfterMs?: number }
|
|
150
|
+
| { kind: "terminal" | "unavailable" | "unknown" };
|
|
151
|
+
recordRuntimeEvent?: (
|
|
152
|
+
category: string,
|
|
153
|
+
error: unknown,
|
|
154
|
+
details?: Record<string, unknown>,
|
|
155
|
+
) => void;
|
|
156
|
+
setTimer?: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
|
|
157
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface TelegramBindDeliveryHandle {
|
|
161
|
+
readonly target: { chatId: number; threadId?: number };
|
|
162
|
+
readonly messageIds: readonly number[];
|
|
163
|
+
readonly generation: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface TelegramBindLiveHandle {
|
|
167
|
+
delivery: TelegramBindDeliveryHandle;
|
|
168
|
+
view?: { text: string; parseMode: "markdown"; replyMarkup?: unknown };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getTelegramBindLiveSurfaceKey(
|
|
172
|
+
app: string,
|
|
173
|
+
profile: string,
|
|
174
|
+
target: { chatId: number; threadId?: number },
|
|
175
|
+
): string {
|
|
176
|
+
return `${app}/${profile}/${target.chatId}/${target.threadId ?? 0}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
107
179
|
export interface TelegramBindToolRegistrationDeps extends GenerativeAppRuntimeOptions {
|
|
180
|
+
getActiveProfileName?: () => string | undefined;
|
|
181
|
+
liveSurfaceSetTimer?: GenerativeAppLiveSurfaceRuntimeDeps<unknown>["setTimer"];
|
|
182
|
+
liveSurfaceClearTimer?: GenerativeAppLiveSurfaceRuntimeDeps<unknown>["clearTimer"];
|
|
183
|
+
setLiveSurfaceRuntime?: (
|
|
184
|
+
runtime: GenerativeAppLiveSurfaceRuntime<TelegramBindLiveHandle> | undefined,
|
|
185
|
+
) => void;
|
|
186
|
+
isDeliveryHandleCurrent?: (handle: TelegramBindDeliveryHandle) => boolean;
|
|
187
|
+
editView?: (
|
|
188
|
+
handle: TelegramBindDeliveryHandle,
|
|
189
|
+
view: { text: string; parseMode: "markdown"; replyMarkup?: unknown },
|
|
190
|
+
) => Promise<
|
|
191
|
+
| { ok: true; value: TelegramBindDeliveryHandle }
|
|
192
|
+
| { ok: false; reason: string; message: string; retryAfterMs?: number }
|
|
193
|
+
>;
|
|
108
194
|
getActiveTurn?: () =>
|
|
109
195
|
| {
|
|
110
196
|
chatId: number;
|
|
@@ -125,6 +211,13 @@ export interface TelegramBindToolRegistrationDeps extends GenerativeAppRuntimeOp
|
|
|
125
211
|
target?: { chatId: number; threadId?: number };
|
|
126
212
|
},
|
|
127
213
|
) => Promise<number | undefined>;
|
|
214
|
+
sendView?: (
|
|
215
|
+
view: { text: string; parseMode: "markdown"; replyMarkup?: unknown },
|
|
216
|
+
options: { scope: { kind: "active-turn" }; replyToMessageId: number },
|
|
217
|
+
) => Promise<
|
|
218
|
+
| { ok: true; value: TelegramBindDeliveryHandle }
|
|
219
|
+
| { ok: false; reason?: string; message: string }
|
|
220
|
+
>;
|
|
128
221
|
recordRuntimeEvent?: (
|
|
129
222
|
category: string,
|
|
130
223
|
error: unknown,
|
|
@@ -365,8 +458,24 @@ function normalizeMethodResult(value: unknown): GenerativeAppMethodResult {
|
|
|
365
458
|
if (viewMode !== "new" && viewMode !== "edit") {
|
|
366
459
|
throw new Error("Generative App viewMode must be new or edit.");
|
|
367
460
|
}
|
|
461
|
+
let refreshAfterMs: number | undefined;
|
|
462
|
+
if (Object.hasOwn(result, "refreshAfterMs")) {
|
|
463
|
+
if (
|
|
464
|
+
typeof result.refreshAfterMs !== "number" ||
|
|
465
|
+
!Number.isFinite(result.refreshAfterMs) ||
|
|
466
|
+
!Number.isInteger(result.refreshAfterMs) ||
|
|
467
|
+
result.refreshAfterMs <= 0
|
|
468
|
+
) {
|
|
469
|
+
throw new Error("Generative App refreshAfterMs must be a finite positive integer.");
|
|
470
|
+
}
|
|
471
|
+
refreshAfterMs = Math.min(
|
|
472
|
+
GENERATIVE_APP_MAX_REFRESH_AFTER_MS,
|
|
473
|
+
Math.max(GENERATIVE_APP_MIN_REFRESH_AFTER_MS, result.refreshAfterMs),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
368
476
|
return {
|
|
369
477
|
output: result.output,
|
|
478
|
+
...(refreshAfterMs !== undefined ? { refreshAfterMs } : {}),
|
|
370
479
|
...(Object.hasOwn(result, "state")
|
|
371
480
|
? { state: assertJsonValue(result.state, "Generative App state") }
|
|
372
481
|
: {}),
|
|
@@ -595,6 +704,9 @@ async function invokeMethod(options: {
|
|
|
595
704
|
if (options.method === "init" && result.state === undefined) {
|
|
596
705
|
throw new Error("Generative App init must return state.");
|
|
597
706
|
}
|
|
707
|
+
if (options.method === "refresh" && result.state !== undefined) {
|
|
708
|
+
throw new Error("Generative App refresh must be output-only.");
|
|
709
|
+
}
|
|
598
710
|
let nextRevision = revision;
|
|
599
711
|
if (result.state !== undefined) {
|
|
600
712
|
options.execution?.assertCurrent();
|
|
@@ -623,6 +735,9 @@ async function invokeMethod(options: {
|
|
|
623
735
|
output: result.output,
|
|
624
736
|
app: options.app,
|
|
625
737
|
revision: nextRevision,
|
|
738
|
+
...(result.refreshAfterMs !== undefined
|
|
739
|
+
? { refreshAfterMs: result.refreshAfterMs }
|
|
740
|
+
: {}),
|
|
626
741
|
stateChanged: result.state !== undefined,
|
|
627
742
|
viewMode: result.viewMode ?? "new",
|
|
628
743
|
};
|
|
@@ -848,6 +963,154 @@ export async function bindGenerativeApp(options: GenerativeAppRuntimeOptions & {
|
|
|
848
963
|
});
|
|
849
964
|
}
|
|
850
965
|
|
|
966
|
+
export function createGenerativeAppLiveSurfaceRuntime<THandle>(
|
|
967
|
+
deps: GenerativeAppLiveSurfaceRuntimeDeps<THandle>,
|
|
968
|
+
): GenerativeAppLiveSurfaceRuntime<THandle> {
|
|
969
|
+
interface Record {
|
|
970
|
+
surface: GenerativeAppLiveSurface<THandle>;
|
|
971
|
+
digest?: string;
|
|
972
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
973
|
+
inFlight: boolean;
|
|
974
|
+
retryDelayMs: number;
|
|
975
|
+
pending?: {
|
|
976
|
+
frame: GenerativeAppLiveSurfaceFrame<THandle>;
|
|
977
|
+
result: GenerativeAppInvocationResult;
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
const records = new Map<string, Record>();
|
|
981
|
+
const setTimer: NonNullable<GenerativeAppLiveSurfaceRuntimeDeps<THandle>["setTimer"]> =
|
|
982
|
+
deps.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
983
|
+
const clearTimer: NonNullable<GenerativeAppLiveSurfaceRuntimeDeps<THandle>["clearTimer"]> =
|
|
984
|
+
deps.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
985
|
+
let active = true;
|
|
986
|
+
|
|
987
|
+
const clearRecordTimer = (record: Record): void => {
|
|
988
|
+
if (record.timer !== undefined) clearTimer(record.timer);
|
|
989
|
+
record.timer = undefined;
|
|
990
|
+
};
|
|
991
|
+
const take = (key: string): GenerativeAppLiveSurface<THandle> | undefined => {
|
|
992
|
+
const record = records.get(key);
|
|
993
|
+
if (!record) return undefined;
|
|
994
|
+
clearRecordTimer(record);
|
|
995
|
+
records.delete(key);
|
|
996
|
+
return { ...record.surface };
|
|
997
|
+
};
|
|
998
|
+
const cancel = (key: string): void => {
|
|
999
|
+
take(key);
|
|
1000
|
+
};
|
|
1001
|
+
const ownsRecord = (key: string, record: Record): boolean =>
|
|
1002
|
+
active && records.get(key) === record;
|
|
1003
|
+
const schedule = (record: Record, delayMs: number): void => {
|
|
1004
|
+
clearRecordTimer(record);
|
|
1005
|
+
if (!active || records.get(record.surface.key) !== record) return;
|
|
1006
|
+
const timer = setTimer(() => void refreshNow(record.surface.key), delayMs);
|
|
1007
|
+
record.timer = timer;
|
|
1008
|
+
timer.unref?.();
|
|
1009
|
+
};
|
|
1010
|
+
const refreshNow = async (key: string): Promise<void> => {
|
|
1011
|
+
const record = records.get(key);
|
|
1012
|
+
if (!active || !record || record.inFlight) return;
|
|
1013
|
+
clearRecordTimer(record);
|
|
1014
|
+
if (!deps.isCurrent(record.surface)) {
|
|
1015
|
+
cancel(key);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
record.inFlight = true;
|
|
1019
|
+
try {
|
|
1020
|
+
let pending = record.pending;
|
|
1021
|
+
if (!pending) {
|
|
1022
|
+
const result = await invokeGenerativeApp({
|
|
1023
|
+
agentDir: deps.agentDir,
|
|
1024
|
+
expectedGeneration: record.surface.appGeneration,
|
|
1025
|
+
expectedRevision: record.surface.appRevision,
|
|
1026
|
+
method: "refresh",
|
|
1027
|
+
app: record.surface.app,
|
|
1028
|
+
});
|
|
1029
|
+
if (!ownsRecord(key, record) || !deps.isCurrent(record.surface)) {
|
|
1030
|
+
if (records.get(key) === record) cancel(key);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const frame = deps.plan(result, record.surface.handle);
|
|
1034
|
+
pending = { frame, result };
|
|
1035
|
+
if (frame.digest !== record.digest) record.pending = pending;
|
|
1036
|
+
}
|
|
1037
|
+
const { frame, result } = pending;
|
|
1038
|
+
if (!ownsRecord(key, record)) return;
|
|
1039
|
+
if (frame.digest !== record.digest) {
|
|
1040
|
+
record.surface.handle = await deps.edit(frame);
|
|
1041
|
+
if (!ownsRecord(key, record)) return;
|
|
1042
|
+
record.digest = frame.digest;
|
|
1043
|
+
record.pending = undefined;
|
|
1044
|
+
}
|
|
1045
|
+
if (result.refreshAfterMs === undefined) {
|
|
1046
|
+
cancel(key);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
record.retryDelayMs = GENERATIVE_APP_MIN_REFRESH_AFTER_MS;
|
|
1050
|
+
record.surface.appRevision = result.revision;
|
|
1051
|
+
record.surface.refreshAfterMs = result.refreshAfterMs;
|
|
1052
|
+
schedule(record, result.refreshAfterMs);
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
const classification = deps.classifyEditError?.(error) ?? { kind: "unknown" as const };
|
|
1055
|
+
if (!ownsRecord(key, record)) return;
|
|
1056
|
+
deps.recordRuntimeEvent?.("generative-app", error, {
|
|
1057
|
+
phase: "live-surface-refresh",
|
|
1058
|
+
app: record.surface.app,
|
|
1059
|
+
outcome: classification.kind,
|
|
1060
|
+
});
|
|
1061
|
+
if (classification.kind !== "retry" || !deps.isCurrent(record.surface)) {
|
|
1062
|
+
cancel(key);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const delay = classification.retryAfterMs === undefined
|
|
1066
|
+
? record.retryDelayMs
|
|
1067
|
+
: Math.max(GENERATIVE_APP_MIN_REFRESH_AFTER_MS, classification.retryAfterMs);
|
|
1068
|
+
record.retryDelayMs = Math.min(60_000, Math.max(4_000, delay * 2));
|
|
1069
|
+
schedule(record, delay);
|
|
1070
|
+
} finally {
|
|
1071
|
+
record.inFlight = false;
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
const open = (surface: GenerativeAppLiveSurface<THandle>): void => {
|
|
1075
|
+
cancel(surface.key);
|
|
1076
|
+
if (!active) return;
|
|
1077
|
+
const record: Record = {
|
|
1078
|
+
surface: { ...surface },
|
|
1079
|
+
digest: surface.initialDigest,
|
|
1080
|
+
inFlight: false,
|
|
1081
|
+
retryDelayMs: GENERATIVE_APP_MIN_REFRESH_AFTER_MS,
|
|
1082
|
+
};
|
|
1083
|
+
records.set(surface.key, record);
|
|
1084
|
+
schedule(record, surface.refreshAfterMs);
|
|
1085
|
+
};
|
|
1086
|
+
return {
|
|
1087
|
+
open,
|
|
1088
|
+
cancel,
|
|
1089
|
+
take,
|
|
1090
|
+
async resume(surface, result) {
|
|
1091
|
+
if (!active || !deps.isCurrent(surface)) return;
|
|
1092
|
+
const frame = deps.plan(result, surface.handle);
|
|
1093
|
+
const handle = frame.digest === surface.initialDigest
|
|
1094
|
+
? surface.handle
|
|
1095
|
+
: await deps.edit(frame);
|
|
1096
|
+
if (result.refreshAfterMs === undefined || !deps.isCurrent({ ...surface, handle })) return;
|
|
1097
|
+
open({
|
|
1098
|
+
...surface,
|
|
1099
|
+
appGeneration: result.generation,
|
|
1100
|
+
appRevision: result.revision,
|
|
1101
|
+
handle,
|
|
1102
|
+
initialDigest: frame.digest,
|
|
1103
|
+
refreshAfterMs: result.refreshAfterMs,
|
|
1104
|
+
});
|
|
1105
|
+
},
|
|
1106
|
+
refreshNow,
|
|
1107
|
+
shutdown() {
|
|
1108
|
+
active = false;
|
|
1109
|
+
for (const key of [...records.keys()]) cancel(key);
|
|
1110
|
+
},
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
851
1114
|
export function formatGenerativeAppToolOutput(output: string): string {
|
|
852
1115
|
const normalized = output.replace(/^\n+/u, "");
|
|
853
1116
|
return `\n${normalized || "(Generative App returned no output)"}`;
|
|
@@ -884,6 +1147,72 @@ export function registerTelegramBindTool(
|
|
|
884
1147
|
pi: ExtensionAPI,
|
|
885
1148
|
deps: TelegramBindToolRegistrationDeps,
|
|
886
1149
|
): void {
|
|
1150
|
+
const planFrame = (
|
|
1151
|
+
result: GenerativeAppInvocationResult,
|
|
1152
|
+
handle: TelegramBindLiveHandle,
|
|
1153
|
+
): GenerativeAppLiveSurfaceFrame<TelegramBindLiveHandle> => {
|
|
1154
|
+
const planned = deps.planOutput!(result.output, {
|
|
1155
|
+
binding: {
|
|
1156
|
+
generation: result.generation,
|
|
1157
|
+
app: result.app,
|
|
1158
|
+
revision: result.revision,
|
|
1159
|
+
},
|
|
1160
|
+
});
|
|
1161
|
+
const view = {
|
|
1162
|
+
text: planned.markdown,
|
|
1163
|
+
parseMode: "markdown" as const,
|
|
1164
|
+
...(planned.replyMarkup !== undefined ? { replyMarkup: planned.replyMarkup } : {}),
|
|
1165
|
+
};
|
|
1166
|
+
return {
|
|
1167
|
+
digest: createHash("sha256").update(JSON.stringify(view)).digest("hex"),
|
|
1168
|
+
handle: { delivery: handle.delivery, view },
|
|
1169
|
+
};
|
|
1170
|
+
};
|
|
1171
|
+
const liveSurfaces = deps.planOutput && deps.editView && deps.isDeliveryHandleCurrent
|
|
1172
|
+
? createGenerativeAppLiveSurfaceRuntime<TelegramBindLiveHandle>({
|
|
1173
|
+
agentDir: deps.agentDir,
|
|
1174
|
+
isCurrent: (surface) => deps.isDeliveryHandleCurrent!(surface.handle.delivery),
|
|
1175
|
+
plan: planFrame,
|
|
1176
|
+
async edit(frame) {
|
|
1177
|
+
const view = frame.handle.view;
|
|
1178
|
+
if (!view) throw new Error("Generative App live frame is missing its planned view.");
|
|
1179
|
+
const edited = await deps.editView!(frame.handle.delivery, view);
|
|
1180
|
+
if (!edited.ok) throw Object.assign(new Error(edited.message), {
|
|
1181
|
+
deliveryFailureReason: edited.reason,
|
|
1182
|
+
...(edited.retryAfterMs === undefined ? {} : { retryAfterMs: edited.retryAfterMs }),
|
|
1183
|
+
});
|
|
1184
|
+
return { delivery: edited.value, view };
|
|
1185
|
+
},
|
|
1186
|
+
classifyEditError(error) {
|
|
1187
|
+
const failure = error as {
|
|
1188
|
+
deliveryFailureReason?: string;
|
|
1189
|
+
retryAfterMs?: number;
|
|
1190
|
+
};
|
|
1191
|
+
if (failure.deliveryFailureReason === "rate-limited") {
|
|
1192
|
+
return { kind: "retry", retryAfterMs: failure.retryAfterMs };
|
|
1193
|
+
}
|
|
1194
|
+
if (failure.deliveryFailureReason === "transport-retryable") {
|
|
1195
|
+
return { kind: "retry" };
|
|
1196
|
+
}
|
|
1197
|
+
if (failure.deliveryFailureReason === "message-unavailable") {
|
|
1198
|
+
return { kind: "unavailable" };
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
kind: failure.deliveryFailureReason === "commit-unknown" ? "unknown" : "terminal",
|
|
1202
|
+
};
|
|
1203
|
+
},
|
|
1204
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1205
|
+
...(deps.liveSurfaceSetTimer ? { setTimer: deps.liveSurfaceSetTimer } : {}),
|
|
1206
|
+
...(deps.liveSurfaceClearTimer ? { clearTimer: deps.liveSurfaceClearTimer } : {}),
|
|
1207
|
+
})
|
|
1208
|
+
: undefined;
|
|
1209
|
+
deps.setLiveSurfaceRuntime?.(liveSurfaces);
|
|
1210
|
+
const surfaceKey = (app: string, handle: TelegramBindDeliveryHandle): string =>
|
|
1211
|
+
getTelegramBindLiveSurfaceKey(
|
|
1212
|
+
app,
|
|
1213
|
+
deps.getActiveProfileName?.() ?? "default",
|
|
1214
|
+
handle.target,
|
|
1215
|
+
);
|
|
887
1216
|
pi.registerTool({
|
|
888
1217
|
name: "telegram_bind",
|
|
889
1218
|
label: "Telegram Bind",
|
|
@@ -919,7 +1248,7 @@ export function registerTelegramBindTool(
|
|
|
919
1248
|
const activeTurn = params.display === false
|
|
920
1249
|
? undefined
|
|
921
1250
|
: deps.getActiveTurn?.();
|
|
922
|
-
if (activeTurn && deps.planOutput && deps.sendMarkdownReply) {
|
|
1251
|
+
if (activeTurn && deps.planOutput && (deps.sendView || deps.sendMarkdownReply)) {
|
|
923
1252
|
try {
|
|
924
1253
|
const planned = deps.planOutput(result.output, {
|
|
925
1254
|
binding: {
|
|
@@ -928,17 +1257,54 @@ export function registerTelegramBindTool(
|
|
|
928
1257
|
revision: result.revision,
|
|
929
1258
|
},
|
|
930
1259
|
});
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1260
|
+
let messageId: number | undefined;
|
|
1261
|
+
if (deps.sendView) {
|
|
1262
|
+
const delivered = await deps.sendView(
|
|
1263
|
+
{
|
|
1264
|
+
text: planned.markdown,
|
|
1265
|
+
parseMode: "markdown",
|
|
1266
|
+
...(planned.replyMarkup !== undefined
|
|
1267
|
+
? { replyMarkup: planned.replyMarkup }
|
|
1268
|
+
: {}),
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
scope: { kind: "active-turn" },
|
|
1272
|
+
replyToMessageId: activeTurn.replyToMessageId,
|
|
1273
|
+
},
|
|
1274
|
+
);
|
|
1275
|
+
if (!delivered.ok) throw new Error(delivered.message);
|
|
1276
|
+
messageId = delivered.value.messageIds[0];
|
|
1277
|
+
if (liveSurfaces) {
|
|
1278
|
+
const key = surfaceKey(result.app, delivered.value);
|
|
1279
|
+
if (result.refreshAfterMs === undefined) {
|
|
1280
|
+
liveSurfaces.cancel(key);
|
|
1281
|
+
} else {
|
|
1282
|
+
const handle = { delivery: delivered.value };
|
|
1283
|
+
const frame = planFrame(result, handle);
|
|
1284
|
+
liveSurfaces.open({
|
|
1285
|
+
app: result.app,
|
|
1286
|
+
appGeneration: result.generation,
|
|
1287
|
+
appRevision: result.revision,
|
|
1288
|
+
handle,
|
|
1289
|
+
initialDigest: frame.digest,
|
|
1290
|
+
key,
|
|
1291
|
+
refreshAfterMs: result.refreshAfterMs,
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
} else {
|
|
1296
|
+
messageId = await deps.sendMarkdownReply!(
|
|
1297
|
+
activeTurn.chatId,
|
|
1298
|
+
activeTurn.replyToMessageId,
|
|
1299
|
+
planned.markdown,
|
|
1300
|
+
{
|
|
1301
|
+
...(planned.replyMarkup !== undefined
|
|
1302
|
+
? { replyMarkup: planned.replyMarkup }
|
|
1303
|
+
: {}),
|
|
1304
|
+
...(activeTurn.target ? { target: activeTurn.target } : {}),
|
|
1305
|
+
},
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
942
1308
|
return {
|
|
943
1309
|
content: [{ type: "text", text: formatDisplayedGenerativeAppToolOutput() }],
|
|
944
1310
|
details: { ...result, displayed: true, messageId },
|
package/lib/menu-settings.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
|
23
23
|
|
|
24
24
|
export interface TelegramSettingsStateDeps {
|
|
25
25
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode | undefined;
|
|
26
|
+
getThreadDisplayPreview?: (mode: TelegramThreadDisplayMode) => readonly string[];
|
|
26
27
|
areDraftPreviewsEnabled: () => boolean;
|
|
27
28
|
getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
|
|
28
29
|
getActivityVerbosity: () => TelegramActivityVerbosity;
|
|
@@ -175,7 +176,15 @@ export function buildTelegramSettingsMenuText(): string {
|
|
|
175
176
|
return SETTINGS_MENU_TITLE;
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
function escapeThreadDisplayPreview(text: string): string {
|
|
180
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function buildThreadDisplaySettingsText(
|
|
184
|
+
mode: TelegramThreadDisplayMode,
|
|
185
|
+
preview: readonly string[] = [],
|
|
186
|
+
): string {
|
|
187
|
+
const visiblePreview = preview.slice(0, 5).map(escapeThreadDisplayPreview);
|
|
179
188
|
return [
|
|
180
189
|
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${mode}</code>`,
|
|
181
190
|
"",
|
|
@@ -183,7 +192,10 @@ export function buildThreadDisplaySettingsText(mode: TelegramThreadDisplayMode):
|
|
|
183
192
|
"",
|
|
184
193
|
"<code>-</code> <code>letters</code> (default): show the unique slot, such as <b><i>A</i></b> or <b><i>B</i></b>.",
|
|
185
194
|
"<code>-</code> <code>names</code>: show the generated dictionary name for the slot, such as <b><i>Anchor</i></b> or <b><i>Briar</i></b>.",
|
|
186
|
-
"<code>-</code> <code>directories</code>:
|
|
195
|
+
"<code>-</code> <code>directories</code>: legacy directory formatting retained without automatic migration.",
|
|
196
|
+
"<code>-</code> <code>directory-snake</code>: normalize the distinguishing directory path, such as <b><i>api_tools</i></b> or <b><i>frontend_extensions_a</i></b>.",
|
|
197
|
+
"<code>-</code> <code>directory-title</code>: humanize the distinguishing directory path, such as <b><i>Api Tools</i></b> or <b><i>Frontend / Extensions · A</i></b>.",
|
|
198
|
+
...(visiblePreview.length ? ["", `<b>Live preview:</b> ${visiblePreview.map((label) => `<code>${label}</code>`).join(", ")}${preview.length > visiblePreview.length ? `, +${preview.length - visiblePreview.length} more` : ""}`] : []),
|
|
187
199
|
].join("\n");
|
|
188
200
|
}
|
|
189
201
|
|
|
@@ -414,7 +426,7 @@ export async function openTelegramSettingsMenu<
|
|
|
414
426
|
export function buildThreadDisplaySettingsReplyMarkup(mode: TelegramThreadDisplayMode): TelegramSettingsMenuReplyMarkup {
|
|
415
427
|
return { inline_keyboard: [
|
|
416
428
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
417
|
-
...(["letters", "names", "directories"] as const).map((value) => [{
|
|
429
|
+
...(["letters", "names", "directories", "directory-snake", "directory-title"] as const).map((value) => [{
|
|
418
430
|
text: `${mode === value ? "🟢 " : ""}${value}`,
|
|
419
431
|
callback_data: `settings:set:thread-display:${value}`,
|
|
420
432
|
}]),
|
|
@@ -630,7 +642,8 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
630
642
|
}
|
|
631
643
|
if (data.startsWith("settings:set:thread-display:")) {
|
|
632
644
|
const mode = data.slice("settings:set:thread-display:".length);
|
|
633
|
-
if (mode !== "letters" && mode !== "names" && mode !== "directories"
|
|
645
|
+
if (mode !== "letters" && mode !== "names" && mode !== "directories" &&
|
|
646
|
+
mode !== "directory-snake" && mode !== "directory-title") {
|
|
634
647
|
await deps.answerCallbackQuery(callbackQueryId, "Unknown Thread display mode.");
|
|
635
648
|
return true;
|
|
636
649
|
}
|
|
@@ -647,7 +660,7 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
647
660
|
return true;
|
|
648
661
|
}
|
|
649
662
|
await deps.updateSettingsMessage(
|
|
650
|
-
buildThreadDisplaySettingsText(mode),
|
|
663
|
+
buildThreadDisplaySettingsText(mode, deps.getThreadDisplayPreview?.(mode)),
|
|
651
664
|
buildThreadDisplaySettingsReplyMarkup(mode),
|
|
652
665
|
);
|
|
653
666
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
@@ -863,6 +876,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
863
876
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
864
877
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
865
878
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
879
|
+
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
866
880
|
sendSettingsMenu: (state, text, replyMarkup) =>
|
|
867
881
|
deps.sendInteractiveMessage(
|
|
868
882
|
state.chatId,
|
|
@@ -887,6 +901,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
887
901
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
888
902
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
889
903
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
904
|
+
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
890
905
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
891
906
|
deps.editInteractiveMessage(
|
|
892
907
|
state.chatId,
|
|
@@ -931,6 +946,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
931
946
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
932
947
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
933
948
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
949
|
+
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
934
950
|
setThreadDisplayMode: deps.setThreadDisplayMode,
|
|
935
951
|
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
|
|
936
952
|
setAssistantRenderingMode: deps.setAssistantRenderingMode,
|
package/lib/outbound-markup.ts
CHANGED
|
@@ -504,14 +504,23 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
504
504
|
return undefined;
|
|
505
505
|
};
|
|
506
506
|
|
|
507
|
+
const parseVerticalSequence = (): Record<string, unknown>[][] | undefined => {
|
|
508
|
+
const rows: Record<string, unknown>[][] = [];
|
|
509
|
+
while (offset < source.length) {
|
|
510
|
+
skipWhitespace();
|
|
511
|
+
if (source[offset] !== "{") break;
|
|
512
|
+
const cell = parseCell();
|
|
513
|
+
if (!cell) return undefined;
|
|
514
|
+
rows.push([cell]);
|
|
515
|
+
if (!consumeOptionalSeparator()) return undefined;
|
|
516
|
+
}
|
|
517
|
+
return rows.length > 0 ? rows : undefined;
|
|
518
|
+
};
|
|
519
|
+
|
|
507
520
|
skipWhitespace();
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
rows = cell ? [[cell]] : undefined;
|
|
512
|
-
} else {
|
|
513
|
-
rows = parseMatrix();
|
|
514
|
-
}
|
|
521
|
+
const rows = source[offset] === "{"
|
|
522
|
+
? parseVerticalSequence()
|
|
523
|
+
: parseMatrix();
|
|
515
524
|
if (!rows) return undefined;
|
|
516
525
|
skipWhitespace();
|
|
517
526
|
if (!options.allowTrailing && offset !== source.length) return undefined;
|
package/lib/telegram-api.ts
CHANGED
|
@@ -964,7 +964,7 @@ export function isTelegramApiMethodRetrySafe(method: string): boolean {
|
|
|
964
964
|
return TELEGRAM_RETRY_SAFE_METHODS.has(method);
|
|
965
965
|
}
|
|
966
966
|
|
|
967
|
-
function isRetryableTelegramApiError(error: unknown): boolean {
|
|
967
|
+
export function isRetryableTelegramApiError(error: unknown): boolean {
|
|
968
968
|
return (
|
|
969
969
|
error instanceof TelegramApiHttpError &&
|
|
970
970
|
(error.status === 429 ||
|
|
@@ -972,6 +972,17 @@ function isRetryableTelegramApiError(error: unknown): boolean {
|
|
|
972
972
|
);
|
|
973
973
|
}
|
|
974
974
|
|
|
975
|
+
export function getTelegramApiRetryAfterMs(error: unknown): number | undefined {
|
|
976
|
+
return error instanceof TelegramApiHttpError && error.retryAfterSeconds !== undefined
|
|
977
|
+
? Math.max(0, error.retryAfterSeconds * 1000)
|
|
978
|
+
: undefined;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export function isTelegramMessageUnavailableError(error: unknown): boolean {
|
|
982
|
+
return error instanceof TelegramApiHttpError && error.status === 400 &&
|
|
983
|
+
/Bad Request: (message to edit not found|message not found|message_id_invalid)/iu.test(error.message);
|
|
984
|
+
}
|
|
985
|
+
|
|
975
986
|
function getTelegramRetryDelayMs(
|
|
976
987
|
error: unknown,
|
|
977
988
|
attempt: number,
|