@llblab/pi-kit 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +2 -1
- package/node_modules/@llblab/pi-grow-loop/index.ts +11 -2
- package/node_modules/@llblab/pi-grow-loop/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -4
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +0 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +25 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -5
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
- package/node_modules/@llblab/pi-telegram/index.ts +18 -7
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +49 -13
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +15 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +17 -4
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +56 -2
- package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +65 -4
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +122 -28
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +2 -9
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +20 -18
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +6 -1
- package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +15 -4
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +10 -7
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +49 -12
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/status.ts +4 -2
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +91 -45
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +5 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +17 -9
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -255,7 +255,10 @@ export interface TelegramMenuActionRuntimeDeps<
|
|
|
255
255
|
chatId: number,
|
|
256
256
|
replyToMessageId: number,
|
|
257
257
|
text: string,
|
|
258
|
-
options?: {
|
|
258
|
+
options?: {
|
|
259
|
+
target?: { chatId: number; threadId?: number };
|
|
260
|
+
parseMode?: "HTML";
|
|
261
|
+
},
|
|
259
262
|
) => Promise<unknown>;
|
|
260
263
|
sectionRegistry?: TelegramSectionRegistry;
|
|
261
264
|
isVoiceReplyActive?: () => boolean;
|
|
@@ -798,8 +801,8 @@ export function createTelegramMenuActionRuntime<
|
|
|
798
801
|
await deps.sendTextReply(
|
|
799
802
|
chatId,
|
|
800
803
|
replyToMessageId,
|
|
801
|
-
"Cannot open status while Pi is busy. Send /abort, /next, or /stop
|
|
802
|
-
{ target: { chatId, threadId } },
|
|
804
|
+
"<b>⏳ Cannot open status while Pi is busy. Send /abort, /next, or /stop.</b>",
|
|
805
|
+
{ target: { chatId, threadId }, parseMode: "HTML" },
|
|
803
806
|
);
|
|
804
807
|
},
|
|
805
808
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
|
|
@@ -835,16 +838,16 @@ export function createTelegramMenuActionRuntime<
|
|
|
835
838
|
await deps.sendTextReply(
|
|
836
839
|
chatId,
|
|
837
840
|
replyToMessageId,
|
|
838
|
-
"Cannot switch model while Pi is busy. Send /abort, /next, or /stop
|
|
839
|
-
{ target: { chatId, threadId } },
|
|
841
|
+
"<b>⏳ Cannot switch model while Pi is busy. Send /abort, /next, or /stop.</b>",
|
|
842
|
+
{ target: { chatId, threadId }, parseMode: "HTML" },
|
|
840
843
|
);
|
|
841
844
|
},
|
|
842
845
|
sendNoModelsMessage: async () => {
|
|
843
846
|
await deps.sendTextReply(
|
|
844
847
|
chatId,
|
|
845
848
|
replyToMessageId,
|
|
846
|
-
"No available models with configured auth
|
|
847
|
-
{ target: { chatId, threadId } },
|
|
849
|
+
"<b>🚫 No available models with configured auth.</b>",
|
|
850
|
+
{ target: { chatId, threadId }, parseMode: "HTML" },
|
|
848
851
|
);
|
|
849
852
|
},
|
|
850
853
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
|
|
@@ -14,11 +14,11 @@ export const TELEGRAM_DISCONNECTED_CONTEXT_MESSAGE =
|
|
|
14
14
|
|
|
15
15
|
const LOCAL_SYSTEM_PROMPT_SUFFIX = `
|
|
16
16
|
|
|
17
|
-
${TELEGRAM_CONNECTED_CONTEXT_MESSAGE}
|
|
17
|
+
${TELEGRAM_CONNECTED_CONTEXT_MESSAGE} For Telegram work, consult bundled Skills in routing order: \`telegram-bridge\` for the transport and turn protocol, \`generated-control-surface\` when contextual controls materially shorten feedback, then \`generative-apps\` when the interaction warrants a reusable deterministic app. Load a Skill only if its instructions are not already present in the current context. Do not use Telegram-specific features from unrelated local/TUI prompts.`;
|
|
18
18
|
|
|
19
19
|
const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
|
|
20
20
|
|
|
21
|
-
Telegram turn note:
|
|
21
|
+
Telegram turn note: Follow the applicable bundled Telegram Skills in routing order; load only missing instructions.`;
|
|
22
22
|
|
|
23
23
|
export const TELEGRAM_ATTACH_PROMPT_SNIPPET =
|
|
24
24
|
"Queue files for the active Telegram reply; outside Telegram turns, send files directly to Telegram.";
|
|
@@ -427,12 +427,22 @@ export function createTelegramTransportStampedQueueStore<TContext>(
|
|
|
427
427
|
};
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
export function isTelegramQueueItemSkipped<TContext = unknown>(
|
|
431
|
+
item: TelegramQueueItem<TContext>,
|
|
432
|
+
): boolean {
|
|
433
|
+
return item.kind === "prompt" && Boolean(item.reactionSuppressionEmoji);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function countExecutableTelegramQueueItems<TContext = unknown>(
|
|
437
|
+
items: readonly TelegramQueueItem<TContext>[],
|
|
438
|
+
): number {
|
|
439
|
+
return items.filter((item) => !isTelegramQueueItemSkipped(item)).length;
|
|
440
|
+
}
|
|
441
|
+
|
|
430
442
|
export function createTelegramQueueItemCountGetter<TContext = unknown>(
|
|
431
443
|
store: Pick<TelegramQueueStore<TContext>, "getQueuedItems">,
|
|
432
444
|
): () => number {
|
|
433
|
-
return () =>
|
|
434
|
-
return store.getQueuedItems().length;
|
|
435
|
-
};
|
|
445
|
+
return () => countExecutableTelegramQueueItems(store.getQueuedItems());
|
|
436
446
|
}
|
|
437
447
|
|
|
438
448
|
export function createTelegramActiveTurnStore<
|
|
@@ -993,7 +1003,8 @@ export function consumeDispatchedTelegramPrompt<TContext = unknown>(
|
|
|
993
1003
|
export function formatQueuedTelegramItemsStatus<TContext = unknown>(
|
|
994
1004
|
items: TelegramQueueItem<TContext>[],
|
|
995
1005
|
): string {
|
|
996
|
-
|
|
1006
|
+
const count = countExecutableTelegramQueueItems(items);
|
|
1007
|
+
return count === 0 ? "" : ` +${count}`;
|
|
997
1008
|
}
|
|
998
1009
|
|
|
999
1010
|
export function truncateTelegramQueueSummary(
|
|
@@ -2062,6 +2073,7 @@ export interface TelegramSessionStartRuntimeDeps<TContext, TModel = unknown> {
|
|
|
2062
2073
|
export interface TelegramSessionShutdownRuntimeDeps<TQueueItem> {
|
|
2063
2074
|
isSessionActive?: () => boolean;
|
|
2064
2075
|
unbindDeferredDispatchContext?: () => void;
|
|
2076
|
+
discardQueuedItems?: () => void;
|
|
2065
2077
|
applyState: (state: TelegramSessionShutdownState<TQueueItem>) => void;
|
|
2066
2078
|
clearPendingMediaGroups: () => void;
|
|
2067
2079
|
clearModelMenuState: () => void;
|
|
@@ -2090,6 +2102,7 @@ export interface TelegramSessionLifecycleHookRuntimeDeps<
|
|
|
2090
2102
|
updateStatus: (ctx: TContext) => void;
|
|
2091
2103
|
isSessionActive?: (ctx: TContext) => boolean;
|
|
2092
2104
|
unbindDeferredDispatchContext?: () => void;
|
|
2105
|
+
discardQueuedItems?: (ctx: TContext) => void;
|
|
2093
2106
|
applySessionShutdownState: (
|
|
2094
2107
|
state: TelegramSessionShutdownState<TQueueItem>,
|
|
2095
2108
|
) => void;
|
|
@@ -2286,6 +2299,7 @@ export async function shutdownTelegramSessionRuntime<TQueueItem>(
|
|
|
2286
2299
|
deps.unbindDeferredDispatchContext?.();
|
|
2287
2300
|
await deps.stopPolling();
|
|
2288
2301
|
if (deps.isSessionActive?.() === false) return;
|
|
2302
|
+
deps.discardQueuedItems?.();
|
|
2289
2303
|
deps.applyState(buildTelegramSessionShutdownState<TQueueItem>());
|
|
2290
2304
|
deps.clearPendingMediaGroups();
|
|
2291
2305
|
deps.clearModelMenuState();
|
|
@@ -2339,6 +2353,7 @@ export function createTelegramSessionLifecycleRuntime<
|
|
|
2339
2353
|
updateStatus: deps.updateStatus,
|
|
2340
2354
|
isSessionActive: deps.isSessionActive,
|
|
2341
2355
|
unbindDeferredDispatchContext: deps.unbindDeferredDispatchContext,
|
|
2356
|
+
discardQueuedItems: deps.discardQueuedItems,
|
|
2342
2357
|
applySessionShutdownState: stateApplier.applyShutdownState,
|
|
2343
2358
|
clearPendingMediaGroups: deps.clearPendingMediaGroups,
|
|
2344
2359
|
clearModelMenuState: deps.clearModelMenuState,
|
|
@@ -2387,6 +2402,10 @@ export function createTelegramSessionLifecycleHooks<
|
|
|
2387
2402
|
isSessionActive: () =>
|
|
2388
2403
|
ctx === undefined ? true : (deps.isSessionActive?.(ctx) ?? true),
|
|
2389
2404
|
unbindDeferredDispatchContext: deps.unbindDeferredDispatchContext,
|
|
2405
|
+
discardQueuedItems:
|
|
2406
|
+
ctx === undefined || !deps.discardQueuedItems
|
|
2407
|
+
? undefined
|
|
2408
|
+
: () => deps.discardQueuedItems!(ctx),
|
|
2390
2409
|
applyState: deps.applySessionShutdownState,
|
|
2391
2410
|
clearPendingMediaGroups: deps.clearPendingMediaGroups,
|
|
2392
2411
|
clearModelMenuState: deps.clearModelMenuState,
|
|
@@ -2476,14 +2495,8 @@ export function clearTelegramQueueItemsRuntime<TContext>(
|
|
|
2476
2495
|
const removedItems = deps.getQueuedItems();
|
|
2477
2496
|
const removedCount = removedItems.length;
|
|
2478
2497
|
if (removedCount === 0) return 0;
|
|
2498
|
+
deps.onItemsDiscarded?.(removedItems, deps.ctx);
|
|
2479
2499
|
deps.setQueuedItems([]);
|
|
2480
|
-
try {
|
|
2481
|
-
deps.onItemsDiscarded?.(removedItems, deps.ctx);
|
|
2482
|
-
} catch (error) {
|
|
2483
|
-
deps.recordRuntimeEvent?.("queue", error, {
|
|
2484
|
-
phase: "discard-receipt-settlement",
|
|
2485
|
-
});
|
|
2486
|
-
}
|
|
2487
2500
|
updateTelegramQueueStatusRuntime(deps);
|
|
2488
2501
|
return removedCount;
|
|
2489
2502
|
}
|
|
@@ -2877,6 +2890,7 @@ export interface TelegramQueueDispatchControllerDeps<
|
|
|
2877
2890
|
item: PendingTelegramControlItem<TContext>,
|
|
2878
2891
|
ctx: TContext,
|
|
2879
2892
|
) => void;
|
|
2893
|
+
onPromptSkipped?: (item: PendingTelegramTurn, ctx: TContext) => boolean;
|
|
2880
2894
|
}
|
|
2881
2895
|
|
|
2882
2896
|
export interface TelegramQueueDispatchController<TContext = unknown> {
|
|
@@ -2940,6 +2954,7 @@ export function createTelegramQueueDispatchRuntime<TContext = unknown>(
|
|
|
2940
2954
|
deps.hasPendingInboundQueueMutationForItem,
|
|
2941
2955
|
isQueueItemAdmissionReady: deps.isQueueItemAdmissionReady,
|
|
2942
2956
|
onControlSettled: deps.onControlSettled,
|
|
2957
|
+
onPromptSkipped: deps.onPromptSkipped,
|
|
2943
2958
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
2944
2959
|
});
|
|
2945
2960
|
}
|
|
@@ -3007,7 +3022,29 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
3007
3022
|
deps.updateStatus(ctx);
|
|
3008
3023
|
return;
|
|
3009
3024
|
}
|
|
3025
|
+
try {
|
|
3026
|
+
if (deps.onPromptSkipped && !deps.onPromptSkipped(candidate, ctx)) {
|
|
3027
|
+
deps.updateStatus(
|
|
3028
|
+
ctx,
|
|
3029
|
+
"Telegram skipped prompt could not be settled durably.",
|
|
3030
|
+
);
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
} catch (error) {
|
|
3034
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
3035
|
+
phase: "skip-receipt-settlement",
|
|
3036
|
+
});
|
|
3037
|
+
deps.updateStatus(
|
|
3038
|
+
ctx,
|
|
3039
|
+
"Telegram skipped prompt could not be settled durably.",
|
|
3040
|
+
);
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3010
3043
|
nextActiveIndex += 1;
|
|
3044
|
+
deps.setQueuedItems([
|
|
3045
|
+
...activeItems.slice(nextActiveIndex),
|
|
3046
|
+
...protectedInactiveItems,
|
|
3047
|
+
]);
|
|
3011
3048
|
}
|
|
3012
3049
|
}
|
|
3013
3050
|
const dispatchableItems = activeItems.slice(nextActiveIndex);
|
|
@@ -3031,7 +3068,7 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
3031
3068
|
dispatchableItems,
|
|
3032
3069
|
canDispatch,
|
|
3033
3070
|
);
|
|
3034
|
-
if (
|
|
3071
|
+
if (dispatchPlan.kind !== "none") {
|
|
3035
3072
|
deps.setQueuedItems([
|
|
3036
3073
|
...dispatchPlan.remainingItems,
|
|
3037
3074
|
...protectedInactiveItems,
|
|
@@ -1677,9 +1677,12 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1677
1677
|
})
|
|
1678
1678
|
: undefined,
|
|
1679
1679
|
stopTypingLoop: deps.stopTypingLoop,
|
|
1680
|
-
sendTextReply: (text) =>
|
|
1680
|
+
sendTextReply: (text, options) =>
|
|
1681
1681
|
deps
|
|
1682
|
-
.sendTextReply(chatId, replyToMessageId, text, {
|
|
1682
|
+
.sendTextReply(chatId, replyToMessageId, text, {
|
|
1683
|
+
target,
|
|
1684
|
+
parseMode: options?.parseMode,
|
|
1685
|
+
})
|
|
1683
1686
|
.then(() => {}),
|
|
1684
1687
|
suppressStartNotice: true,
|
|
1685
1688
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
@@ -344,6 +344,7 @@ export interface TelegramBridgeStatusRuntimeDeps<
|
|
|
344
344
|
getActiveToolExecutions: () => number;
|
|
345
345
|
hasPendingModelSwitch: () => boolean;
|
|
346
346
|
getQueuedItems: () => TQueueItem[];
|
|
347
|
+
getQueuedItemCount?: (items: TQueueItem[]) => number;
|
|
347
348
|
formatQueuedStatus: (items: TQueueItem[]) => string;
|
|
348
349
|
getRecentRuntimeEvents: () => TelegramRuntimeEvent[];
|
|
349
350
|
getRuntimeLockState?: () => string;
|
|
@@ -658,6 +659,7 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
658
659
|
getStatusBarState: (_ctx, error) => {
|
|
659
660
|
const config = deps.getConfig();
|
|
660
661
|
const queuedItems = deps.getQueuedItems();
|
|
662
|
+
const queuedItemCount = deps.getQueuedItemCount?.(queuedItems) ?? queuedItems.length;
|
|
661
663
|
const hasActiveTurn = deps.hasActiveTurn();
|
|
662
664
|
const hasPendingDispatch = deps.hasDispatchPending();
|
|
663
665
|
const hasPendingModelSwitch = deps.hasPendingModelSwitch();
|
|
@@ -677,13 +679,13 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
677
679
|
hasPendingDispatch ||
|
|
678
680
|
hasPendingModelSwitch ||
|
|
679
681
|
activeToolExecutions > 0 ||
|
|
680
|
-
|
|
682
|
+
queuedItemCount > 0,
|
|
681
683
|
processingStatus: getTelegramStatusBarProcessingStatus({
|
|
682
684
|
hasActiveTurn,
|
|
683
685
|
hasPendingDispatch,
|
|
684
686
|
hasPendingModelSwitch,
|
|
685
687
|
activeToolExecutions,
|
|
686
|
-
queuedItems:
|
|
688
|
+
queuedItems: queuedItemCount,
|
|
687
689
|
}),
|
|
688
690
|
queuedStatus: deps.formatQueuedStatus(queuedItems),
|
|
689
691
|
error,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Owns pure contracts for deciding when local Telegram mirror state should be refreshed without querying Telegram on every action
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { getTelegramApiErrorRequestTarget } from "./telegram-api.ts";
|
|
7
8
|
import { getTelegramTargetKey, type TelegramTarget } from "./target.ts";
|
|
8
9
|
import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
9
10
|
import {
|
|
@@ -423,6 +424,22 @@ export function createTelegramStaleTopicApiErrorRecoveryRuntime<
|
|
|
423
424
|
recoverStaleTelegramTopicApiError(apiBody, error, deps);
|
|
424
425
|
}
|
|
425
426
|
|
|
427
|
+
export async function settleStaleTelegramTopicExecutionFailure<
|
|
428
|
+
TSyncState extends TelegramSyncState,
|
|
429
|
+
>(
|
|
430
|
+
error: unknown,
|
|
431
|
+
deps: TelegramStaleTopicApiErrorRecoveryDeps<TSyncState>,
|
|
432
|
+
): Promise<boolean> {
|
|
433
|
+
const target = getTelegramApiErrorRequestTarget(error);
|
|
434
|
+
if (!target || !isTelegramTopicTargetStaleError(error)) return false;
|
|
435
|
+
await recoverStaleTelegramTopicApiError(
|
|
436
|
+
{ chat_id: target.chatId, message_thread_id: target.threadId },
|
|
437
|
+
error,
|
|
438
|
+
deps,
|
|
439
|
+
);
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
426
443
|
export async function recoverStaleTelegramTopicApiError<
|
|
427
444
|
TSyncState extends TelegramSyncState,
|
|
428
445
|
>(
|
|
@@ -587,6 +587,7 @@ export function isTelegramApiCommitUnknownError(
|
|
|
587
587
|
class TelegramApiHttpError extends Error {
|
|
588
588
|
readonly status: number | undefined;
|
|
589
589
|
readonly retryAfterSeconds: number | undefined;
|
|
590
|
+
requestTarget?: { chatId: number; threadId: number };
|
|
590
591
|
constructor(
|
|
591
592
|
message: string,
|
|
592
593
|
status: number | undefined,
|
|
@@ -598,6 +599,41 @@ class TelegramApiHttpError extends Error {
|
|
|
598
599
|
}
|
|
599
600
|
}
|
|
600
601
|
|
|
602
|
+
function attachTelegramApiRequestTarget(
|
|
603
|
+
error: unknown,
|
|
604
|
+
body: Record<string, unknown> | Record<string, string>,
|
|
605
|
+
): void {
|
|
606
|
+
if (!(error instanceof TelegramApiHttpError)) return;
|
|
607
|
+
const chatId = Number(body.chat_id);
|
|
608
|
+
const threadId = Number(body.message_thread_id);
|
|
609
|
+
if (!Number.isSafeInteger(chatId) || !Number.isSafeInteger(threadId)) return;
|
|
610
|
+
error.requestTarget = { chatId, threadId };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export class TelegramApiStaleTargetError extends Error {
|
|
614
|
+
readonly requestTarget: { chatId: number; threadId: number };
|
|
615
|
+
|
|
616
|
+
constructor(
|
|
617
|
+
message: string,
|
|
618
|
+
requestTarget: { chatId: number; threadId: number },
|
|
619
|
+
) {
|
|
620
|
+
super(message);
|
|
621
|
+
this.name = "TelegramApiStaleTargetError";
|
|
622
|
+
this.requestTarget = { ...requestTarget };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function getTelegramApiErrorRequestTarget(
|
|
627
|
+
error: unknown,
|
|
628
|
+
): { chatId: number; threadId: number } | undefined {
|
|
629
|
+
const target =
|
|
630
|
+
error instanceof TelegramApiHttpError ||
|
|
631
|
+
error instanceof TelegramApiStaleTargetError
|
|
632
|
+
? error.requestTarget
|
|
633
|
+
: undefined;
|
|
634
|
+
return target ? { ...target } : undefined;
|
|
635
|
+
}
|
|
636
|
+
|
|
601
637
|
export function isTelegramMessageNotModifiedError(error: unknown): boolean {
|
|
602
638
|
return (
|
|
603
639
|
error instanceof Error && error.message.includes("message is not modified")
|
|
@@ -1156,21 +1192,26 @@ export async function callTelegram<TResponse>(
|
|
|
1156
1192
|
options?: TelegramApiCallOptions,
|
|
1157
1193
|
): Promise<TResponse> {
|
|
1158
1194
|
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1195
|
+
try {
|
|
1196
|
+
return await callTelegramWithRetry(
|
|
1197
|
+
method,
|
|
1198
|
+
async (family) =>
|
|
1199
|
+
telegramFetch(
|
|
1200
|
+
`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
|
|
1201
|
+
{
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
headers: { "content-type": "application/json" },
|
|
1204
|
+
body: JSON.stringify(body),
|
|
1205
|
+
signal: options?.signal,
|
|
1206
|
+
},
|
|
1207
|
+
family,
|
|
1208
|
+
),
|
|
1209
|
+
options,
|
|
1210
|
+
);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
attachTelegramApiRequestTarget(error, body);
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1174
1215
|
}
|
|
1175
1216
|
|
|
1176
1217
|
export type TelegramBotIdentityResponse = Pick<
|
|
@@ -1206,43 +1247,48 @@ export async function callTelegramMultipart<TResponse>(
|
|
|
1206
1247
|
): Promise<TResponse> {
|
|
1207
1248
|
const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
|
|
1208
1249
|
const fileBlob = await openAsBlob(filePath);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1250
|
+
try {
|
|
1251
|
+
return await callTelegramWithRetry(
|
|
1252
|
+
method,
|
|
1253
|
+
async (family) => {
|
|
1254
|
+
if (family) {
|
|
1255
|
+
const multipart = await buildTelegramMultipartBody(
|
|
1256
|
+
fields,
|
|
1257
|
+
fileField,
|
|
1258
|
+
fileBlob,
|
|
1259
|
+
fileName,
|
|
1260
|
+
);
|
|
1261
|
+
return telegramFetch(
|
|
1262
|
+
`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
|
|
1263
|
+
{
|
|
1264
|
+
method: "POST",
|
|
1265
|
+
headers: { "content-type": multipart.contentType },
|
|
1266
|
+
body: multipart.body as unknown as BodyInit,
|
|
1267
|
+
signal: options?.signal,
|
|
1268
|
+
},
|
|
1269
|
+
family,
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
const form = new FormData();
|
|
1273
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
1274
|
+
form.set(key, value);
|
|
1275
|
+
}
|
|
1276
|
+
form.set(fileField, fileBlob, fileName);
|
|
1219
1277
|
return telegramFetch(
|
|
1220
1278
|
`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
|
|
1221
1279
|
{
|
|
1222
1280
|
method: "POST",
|
|
1223
|
-
|
|
1224
|
-
body: multipart.body as unknown as BodyInit,
|
|
1281
|
+
body: form,
|
|
1225
1282
|
signal: options?.signal,
|
|
1226
1283
|
},
|
|
1227
|
-
family,
|
|
1228
1284
|
);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
|
|
1237
|
-
{
|
|
1238
|
-
method: "POST",
|
|
1239
|
-
body: form,
|
|
1240
|
-
signal: options?.signal,
|
|
1241
|
-
},
|
|
1242
|
-
);
|
|
1243
|
-
},
|
|
1244
|
-
options,
|
|
1245
|
-
);
|
|
1285
|
+
},
|
|
1286
|
+
options,
|
|
1287
|
+
);
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
attachTelegramApiRequestTarget(error, fields);
|
|
1290
|
+
throw error;
|
|
1291
|
+
}
|
|
1246
1292
|
}
|
|
1247
1293
|
|
|
1248
1294
|
export async function downloadTelegramFile(
|
|
@@ -2882,6 +2882,11 @@ export function getTelegramTargetFromApiBody(
|
|
|
2882
2882
|
|
|
2883
2883
|
export function isTelegramTopicTargetStaleError(error: unknown): boolean {
|
|
2884
2884
|
if (!(error instanceof Error)) return false;
|
|
2885
|
+
const status =
|
|
2886
|
+
"status" in error && typeof error.status === "number"
|
|
2887
|
+
? error.status
|
|
2888
|
+
: undefined;
|
|
2889
|
+
if (status !== undefined && status !== 400) return false;
|
|
2885
2890
|
const message = error.message.toLowerCase();
|
|
2886
2891
|
return (
|
|
2887
2892
|
message.includes("topic_id_invalid") ||
|
|
@@ -1782,6 +1782,7 @@ export interface TelegramUpdateWorkerRuntimeDeps<TContext> {
|
|
|
1782
1782
|
classifyExecutionFailure?: (
|
|
1783
1783
|
error: unknown,
|
|
1784
1784
|
) => TelegramUpdateExecutionFailureClassification;
|
|
1785
|
+
settleTerminalExecutionFailure?: (error: unknown) => Promise<boolean>;
|
|
1785
1786
|
scheduleRetry?: (callback: () => void, delayMs: number) => unknown;
|
|
1786
1787
|
cancelRetry?: (handle: unknown) => void;
|
|
1787
1788
|
batchSize?: number;
|
|
@@ -4583,11 +4584,11 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
4583
4584
|
try {
|
|
4584
4585
|
deps.recordRuntimeEvent?.(
|
|
4585
4586
|
"inbound-worker",
|
|
4586
|
-
"
|
|
4587
|
+
"Discarded session-owned queue authority from a confirmed-dead process.",
|
|
4587
4588
|
{
|
|
4588
|
-
phase: "dead-queue-owner-
|
|
4589
|
+
phase: "dead-queue-owner-cleanup",
|
|
4589
4590
|
receiptId,
|
|
4590
|
-
|
|
4591
|
+
removedUpdateCount: result.recoveredUpdateIds.length,
|
|
4591
4592
|
},
|
|
4592
4593
|
);
|
|
4593
4594
|
} catch {
|
|
@@ -4833,13 +4834,20 @@ export function createTelegramUpdateAdmissionWorkerRuntime<
|
|
|
4833
4834
|
});
|
|
4834
4835
|
worker = createTelegramUpdateWorkerRuntime({
|
|
4835
4836
|
...deps,
|
|
4836
|
-
executeUpdate
|
|
4837
|
+
async executeUpdate(update, ctx, signal) {
|
|
4837
4838
|
const typedUpdate = update as TUpdate;
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4839
|
+
try {
|
|
4840
|
+
return await executeUpdate(
|
|
4841
|
+
deps.prepareUpdateForExecution?.(typedUpdate) ?? typedUpdate,
|
|
4842
|
+
ctx,
|
|
4843
|
+
signal,
|
|
4844
|
+
);
|
|
4845
|
+
} catch (error) {
|
|
4846
|
+
if (await deps.settleTerminalExecutionFailure?.(error)) {
|
|
4847
|
+
return { kind: "complete" };
|
|
4848
|
+
}
|
|
4849
|
+
throw error;
|
|
4850
|
+
}
|
|
4843
4851
|
},
|
|
4844
4852
|
});
|
|
4845
4853
|
return worker;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@llblab/pi-actors": "0.50.0",
|
|
44
44
|
"@llblab/pi-codex-usage": "0.9.3",
|
|
45
|
-
"@llblab/pi-grow-loop": "0.7.
|
|
46
|
-
"@llblab/pi-telegram": "0.
|
|
45
|
+
"@llblab/pi-grow-loop": "0.7.3",
|
|
46
|
+
"@llblab/pi-telegram": "0.39.3"
|
|
47
47
|
},
|
|
48
48
|
"bundledDependencies": [
|
|
49
49
|
"@llblab/pi-actors",
|