@llblab/pi-telegram 0.46.0 → 0.48.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/AGENTS.md +4 -2
- package/BACKLOG.md +0 -6
- package/CHANGELOG.md +14 -0
- package/README.md +5 -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 +1604 -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 +115 -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 +1008 -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/README.md +2 -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 +6 -4
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +3 -3
- 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 +7 -0
- package/lib/generative-apps.ts +379 -13
- package/lib/menu-settings.ts +20 -6
- package/lib/outbound-markup.ts +16 -7
- package/lib/status.ts +15 -12
- 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/show-me/SKILL.md +1 -1
- package/skills/show-me/references/telegram-surfaces.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
|
@@ -175,15 +175,18 @@ export function buildTelegramSettingsMenuText(): string {
|
|
|
175
175
|
return SETTINGS_MENU_TITLE;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
export function buildThreadDisplaySettingsText(
|
|
178
|
+
export function buildThreadDisplaySettingsText(
|
|
179
|
+
mode: TelegramThreadDisplayMode,
|
|
180
|
+
): string {
|
|
179
181
|
return [
|
|
180
|
-
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${mode}</code>`,
|
|
182
|
+
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${threadDisplayModeLabel(mode)}</code>`,
|
|
181
183
|
"",
|
|
182
184
|
"Choose how this bot profile labels Telegram tabs and Pi terminal status. Each slot is unique across this bot profile.",
|
|
183
185
|
"",
|
|
184
186
|
"<code>-</code> <code>letters</code> (default): show the unique slot, such as <b><i>A</i></b> or <b><i>B</i></b>.",
|
|
185
187
|
"<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>
|
|
188
|
+
"<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>.",
|
|
189
|
+
"<code>-</code> <code>directory-snake</code>: normalize the distinguishing directory path to lowercase, such as <b><i>api_tools</i></b> or <b><i>frontend_extensions_a</i></b>.",
|
|
187
190
|
].join("\n");
|
|
188
191
|
}
|
|
189
192
|
|
|
@@ -411,11 +414,21 @@ export async function openTelegramSettingsMenu<
|
|
|
411
414
|
deps.storeModelMenuState(state);
|
|
412
415
|
}
|
|
413
416
|
|
|
417
|
+
function threadDisplayModeLabel(mode: TelegramThreadDisplayMode): string {
|
|
418
|
+
switch (mode) {
|
|
419
|
+
case "letters": return "letters";
|
|
420
|
+
case "names": return "names";
|
|
421
|
+
case "directory-title": return "directory-title";
|
|
422
|
+
case "directory-snake": return "directory-snake";
|
|
423
|
+
default: return "letters";
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
414
427
|
export function buildThreadDisplaySettingsReplyMarkup(mode: TelegramThreadDisplayMode): TelegramSettingsMenuReplyMarkup {
|
|
415
428
|
return { inline_keyboard: [
|
|
416
429
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
417
|
-
...(["letters", "names", "
|
|
418
|
-
text: `${mode === value ? "🟢 " : ""}${value}`,
|
|
430
|
+
...(["letters", "names", "directory-title", "directory-snake"] as const).map((value) => [{
|
|
431
|
+
text: `${mode === value ? "🟢 " : ""}${threadDisplayModeLabel(value)}`,
|
|
419
432
|
callback_data: `settings:set:thread-display:${value}`,
|
|
420
433
|
}]),
|
|
421
434
|
] };
|
|
@@ -630,7 +643,8 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
630
643
|
}
|
|
631
644
|
if (data.startsWith("settings:set:thread-display:")) {
|
|
632
645
|
const mode = data.slice("settings:set:thread-display:".length);
|
|
633
|
-
if (mode !== "letters" && mode !== "names" &&
|
|
646
|
+
if (mode !== "letters" && mode !== "names" &&
|
|
647
|
+
mode !== "directory-snake" && mode !== "directory-title") {
|
|
634
648
|
await deps.answerCallbackQuery(callbackQueryId, "Unknown Thread display mode.");
|
|
635
649
|
return true;
|
|
636
650
|
}
|
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/status.ts
CHANGED
|
@@ -711,6 +711,14 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
711
711
|
const hasPendingModelSwitch = deps.hasPendingModelSwitch();
|
|
712
712
|
const activeToolExecutions = deps.getActiveToolExecutions();
|
|
713
713
|
const compactionInProgress = deps.isCompactionInProgress();
|
|
714
|
+
const activeWorkCount =
|
|
715
|
+
hasActiveTurn ||
|
|
716
|
+
hasPendingModelSwitch ||
|
|
717
|
+
activeToolExecutions > 0 ||
|
|
718
|
+
compactionInProgress ||
|
|
719
|
+
(hasPendingDispatch && queuedItemCount === 0)
|
|
720
|
+
? 1
|
|
721
|
+
: 0;
|
|
714
722
|
const localBus = deps.getLocalBus?.();
|
|
715
723
|
return {
|
|
716
724
|
hasBotToken: config.botHasToken ?? Boolean(config.botToken),
|
|
@@ -735,7 +743,10 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
735
743
|
activeToolExecutions,
|
|
736
744
|
queuedItems: queuedItemCount,
|
|
737
745
|
}),
|
|
738
|
-
queuedStatus:
|
|
746
|
+
queuedStatus:
|
|
747
|
+
activeWorkCount + queuedItemCount > 0
|
|
748
|
+
? ` +${activeWorkCount + queuedItemCount}`
|
|
749
|
+
: "",
|
|
739
750
|
pollingStopReason: deps.getPollingState?.().stopReason,
|
|
740
751
|
error,
|
|
741
752
|
};
|
|
@@ -937,24 +948,16 @@ export function buildTelegramStatusBarText(
|
|
|
937
948
|
if (!state.pollingActive && state.busRole !== "follower")
|
|
938
949
|
return `${theme.fg("accent", "telegram")} ${theme.fg("dim", "disconnected")}${queued}`;
|
|
939
950
|
if (state.error) {
|
|
940
|
-
return `${label} ${theme.fg("error", "error")}`;
|
|
951
|
+
return `${label} ${theme.fg("error", "error")}${queued}`;
|
|
941
952
|
}
|
|
942
953
|
if (state.busRole === "follower" && state.followerRegistered === false) {
|
|
943
954
|
return `${label} ${theme.fg("warning", "reconnecting")}${queued}`;
|
|
944
955
|
}
|
|
945
|
-
if (state.processing) {
|
|
946
|
-
const processingStatus = state.queuedStatus
|
|
947
|
-
? "active"
|
|
948
|
-
: (state.processingStatus ?? "processing");
|
|
949
|
-
const processingToken =
|
|
950
|
-
processingStatus === "active" ? "warning" : "accent";
|
|
951
|
-
return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
|
|
952
|
-
}
|
|
953
956
|
if (state.busRole === "follower")
|
|
954
957
|
return `${label} ${theme.fg("success", "follower")}${queued}`;
|
|
955
958
|
if (state.busRole === "leader")
|
|
956
|
-
return `${label} ${theme.fg("success", "leader")}`;
|
|
957
|
-
return `${label} ${theme.fg("success", "connected")}`;
|
|
959
|
+
return `${label} ${theme.fg("success", "leader")}${queued}`;
|
|
960
|
+
return `${label} ${theme.fg("success", "connected")}${queued}`;
|
|
958
961
|
}
|
|
959
962
|
|
|
960
963
|
function formatTelegramBridgeBotStatus(
|
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,
|