@llblab/pi-telegram 0.17.4 → 0.18.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 +67 -32
- package/BACKLOG.md +59 -14
- package/CHANGELOG.md +40 -15
- package/README.md +63 -35
- package/docs/README.md +3 -1
- package/docs/architecture.md +55 -23
- package/docs/callback-namespaces.md +1 -1
- package/docs/inbound.md +1 -1
- package/docs/locks.md +0 -2
- package/docs/multi-instance-bus.md +483 -0
- package/docs/outbound.md +4 -3
- package/docs/public-api.md +12 -10
- package/docs/sections.md +2 -2
- package/docs/ui-style.md +76 -0
- package/index.ts +789 -32
- package/lib/bindings.ts +68 -12
- package/lib/bus-api.ts +314 -0
- package/lib/bus-follower.ts +853 -0
- package/lib/bus-leader.ts +915 -0
- package/lib/bus.ts +866 -0
- package/lib/command-templates.ts +9 -11
- package/lib/commands.ts +133 -47
- package/lib/config.ts +53 -5
- package/lib/lifecycle.ts +23 -7
- package/lib/locks.ts +230 -66
- package/lib/media.ts +30 -2
- package/lib/menu-model.ts +48 -17
- package/lib/menu-queue.ts +51 -20
- package/lib/menu-settings.ts +9 -5
- package/lib/menu-status.ts +3 -0
- package/lib/menu-thinking.ts +3 -0
- package/lib/menu.ts +67 -26
- package/lib/outbound-attachments.ts +102 -17
- package/lib/outbound-buttons.ts +6 -2
- package/lib/outbound-voice.ts +31 -11
- package/lib/outbound.ts +6 -4
- package/lib/ownership.ts +119 -0
- package/lib/pi.ts +26 -3
- package/lib/polling.ts +477 -7
- package/lib/preview.ts +141 -88
- package/lib/prompt-templates.ts +3 -3
- package/lib/prompts.ts +80 -30
- package/lib/queue.ts +193 -91
- package/lib/rendering.ts +0 -25
- package/lib/replies.ts +187 -55
- package/lib/routing.ts +1673 -9
- package/lib/runtime-log.ts +123 -0
- package/lib/runtime.ts +84 -12
- package/lib/sections.ts +28 -21
- package/lib/setup.ts +1 -1
- package/lib/status.ts +532 -9
- package/lib/sync.ts +618 -0
- package/lib/target.ts +49 -0
- package/lib/telegram-api.ts +405 -40
- package/lib/text-groups.ts +5 -1
- package/lib/thread-reconciler.ts +915 -0
- package/lib/threads.ts +2205 -0
- package/lib/turns.ts +48 -3
- package/lib/updates.ts +355 -32
- package/package.json +24 -2
- package/screenshot.png +0 -0
- package/docs/telegram-bot-api-rich-messages.md +0 -890
package/lib/queue.ts
CHANGED
|
@@ -67,9 +67,15 @@ export const TELEGRAM_QUEUE_LANE_CONTRACTS: readonly TelegramQueueLaneContract[]
|
|
|
67
67
|
},
|
|
68
68
|
] as const;
|
|
69
69
|
|
|
70
|
+
export interface TelegramQueueTarget {
|
|
71
|
+
chatId: number;
|
|
72
|
+
threadId?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
export interface TelegramQueueItemBase {
|
|
71
76
|
kind: TelegramQueueItemKind;
|
|
72
77
|
chatId: number;
|
|
78
|
+
target?: TelegramQueueTarget;
|
|
73
79
|
replyToMessageId: number;
|
|
74
80
|
guestQueryId?: string;
|
|
75
81
|
queueOrder: number;
|
|
@@ -123,6 +129,7 @@ export interface TelegramActiveTurnStore<
|
|
|
123
129
|
set: (turn: TTurn) => void;
|
|
124
130
|
clear: () => void;
|
|
125
131
|
getChatId: () => number | undefined;
|
|
132
|
+
getTarget: () => TelegramQueueTarget | undefined;
|
|
126
133
|
getReplyToMessageId: () => number | undefined;
|
|
127
134
|
getGuestQueryId: () => string | undefined;
|
|
128
135
|
getSourceMessageIds: () => number[] | undefined;
|
|
@@ -195,7 +202,7 @@ export function createTelegramQueueStore<TContext = unknown>(
|
|
|
195
202
|
export function createTelegramQueueItemCountGetter<TContext = unknown>(
|
|
196
203
|
store: Pick<TelegramQueueStore<TContext>, "getQueuedItems">,
|
|
197
204
|
): () => number {
|
|
198
|
-
return
|
|
205
|
+
return () => {
|
|
199
206
|
return store.getQueuedItems().length;
|
|
200
207
|
};
|
|
201
208
|
}
|
|
@@ -214,6 +221,8 @@ export function createTelegramActiveTurnStore<
|
|
|
214
221
|
activeTurn = undefined;
|
|
215
222
|
},
|
|
216
223
|
getChatId: () => activeTurn?.chatId,
|
|
224
|
+
getTarget: () =>
|
|
225
|
+
activeTurn?.target ? { ...activeTurn.target } : undefined,
|
|
217
226
|
getReplyToMessageId: () => activeTurn?.replyToMessageId,
|
|
218
227
|
getGuestQueryId: () => activeTurn?.guestQueryId,
|
|
219
228
|
getSourceMessageIds: () => activeTurn?.sourceMessageIds,
|
|
@@ -280,16 +289,40 @@ export function compareTelegramQueueItems<TContext = unknown>(
|
|
|
280
289
|
return left.queueOrder - right.queueOrder;
|
|
281
290
|
}
|
|
282
291
|
|
|
292
|
+
export interface TelegramQueueMessageScope {
|
|
293
|
+
chatId?: number;
|
|
294
|
+
threadId?: number;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isTelegramQueueItemInMessageScope<TContext = unknown>(
|
|
298
|
+
item: TelegramQueueItem<TContext>,
|
|
299
|
+
scope: TelegramQueueMessageScope | undefined,
|
|
300
|
+
): boolean {
|
|
301
|
+
if (!scope) return true;
|
|
302
|
+
if (typeof scope.chatId === "number" && item.chatId !== scope.chatId) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (typeof scope.threadId === "number") {
|
|
306
|
+
return item.target?.threadId === scope.threadId;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
283
311
|
export function removeTelegramQueueItemsByMessageIds<TContext = unknown>(
|
|
284
312
|
items: TelegramQueueItem<TContext>[],
|
|
285
313
|
messageIds: number[],
|
|
314
|
+
scope?: TelegramQueueMessageScope,
|
|
286
315
|
): { items: TelegramQueueItem<TContext>[]; removedCount: number } {
|
|
287
316
|
if (messageIds.length === 0 || items.length === 0) {
|
|
288
317
|
return { items, removedCount: 0 };
|
|
289
318
|
}
|
|
290
319
|
const deletedMessageIds = new Set(messageIds);
|
|
291
320
|
const nextItems = items.filter((item) => {
|
|
292
|
-
if (
|
|
321
|
+
if (
|
|
322
|
+
!isPendingTelegramTurn(item) ||
|
|
323
|
+
!isTelegramQueueItemInMessageScope(item, scope)
|
|
324
|
+
)
|
|
325
|
+
return true;
|
|
293
326
|
return !item.sourceMessageIds.some((messageId) =>
|
|
294
327
|
deletedMessageIds.has(messageId),
|
|
295
328
|
);
|
|
@@ -303,11 +336,13 @@ export function removeTelegramQueueItemsByMessageIds<TContext = unknown>(
|
|
|
303
336
|
export function clearTelegramQueuePromptPriority<TContext = unknown>(
|
|
304
337
|
items: TelegramQueueItem<TContext>[],
|
|
305
338
|
messageId: number,
|
|
339
|
+
scope?: TelegramQueueMessageScope,
|
|
306
340
|
): { items: TelegramQueueItem<TContext>[]; changed: boolean } {
|
|
307
341
|
let changed = false;
|
|
308
342
|
const nextItems = items.map((item) => {
|
|
309
343
|
if (
|
|
310
344
|
!isPendingTelegramTurn(item) ||
|
|
345
|
+
!isTelegramQueueItemInMessageScope(item, scope) ||
|
|
311
346
|
!item.sourceMessageIds.includes(messageId) ||
|
|
312
347
|
item.queueLane !== "priority"
|
|
313
348
|
) {
|
|
@@ -329,11 +364,13 @@ export function prioritizeTelegramQueuePrompt<TContext = unknown>(
|
|
|
329
364
|
messageId: number,
|
|
330
365
|
laneOrder: number,
|
|
331
366
|
priorityEmoji = "⚡",
|
|
367
|
+
scope?: TelegramQueueMessageScope,
|
|
332
368
|
): { items: TelegramQueueItem<TContext>[]; changed: boolean } {
|
|
333
369
|
let changed = false;
|
|
334
370
|
const nextItems = items.map((item) => {
|
|
335
371
|
if (
|
|
336
372
|
!isPendingTelegramTurn(item) ||
|
|
373
|
+
!isTelegramQueueItemInMessageScope(item, scope) ||
|
|
337
374
|
!item.sourceMessageIds.includes(messageId)
|
|
338
375
|
) {
|
|
339
376
|
return item;
|
|
@@ -366,15 +403,6 @@ export function consumeDispatchedTelegramPrompt<TContext = unknown>(
|
|
|
366
403
|
return { activeTurn: nextItem, remainingItems: items.slice(1) };
|
|
367
404
|
}
|
|
368
405
|
|
|
369
|
-
function formatTelegramQueueItemStatusSummary<TContext = unknown>(
|
|
370
|
-
item: TelegramQueueItem<TContext>,
|
|
371
|
-
): string {
|
|
372
|
-
if (item.queueLane === "priority") {
|
|
373
|
-
return `${item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡"} ${item.statusSummary}`;
|
|
374
|
-
}
|
|
375
|
-
return item.statusSummary;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
406
|
export function formatQueuedTelegramItemsStatus<TContext = unknown>(
|
|
379
407
|
items: TelegramQueueItem<TContext>[],
|
|
380
408
|
): string {
|
|
@@ -434,6 +462,7 @@ export function createTelegramDispatchReadinessChecker<TContext>(
|
|
|
434
462
|
|
|
435
463
|
export function buildPendingTelegramControlItem<TContext = unknown>(options: {
|
|
436
464
|
chatId: number;
|
|
465
|
+
target?: TelegramQueueTarget;
|
|
437
466
|
replyToMessageId: number;
|
|
438
467
|
controlType: PendingTelegramControlItem<TContext>["controlType"];
|
|
439
468
|
queueOrder: number;
|
|
@@ -445,6 +474,7 @@ export function buildPendingTelegramControlItem<TContext = unknown>(options: {
|
|
|
445
474
|
kind: "control",
|
|
446
475
|
controlType: options.controlType,
|
|
447
476
|
chatId: options.chatId,
|
|
477
|
+
...(options.target ? { target: options.target } : {}),
|
|
448
478
|
replyToMessageId: options.replyToMessageId,
|
|
449
479
|
queueOrder: options.queueOrder,
|
|
450
480
|
queueLane: "control",
|
|
@@ -463,6 +493,7 @@ export function createTelegramControlItemBuilder<TContext = unknown>(
|
|
|
463
493
|
deps: TelegramControlItemBuilderDeps,
|
|
464
494
|
): (options: {
|
|
465
495
|
chatId: number;
|
|
496
|
+
target?: TelegramQueueTarget;
|
|
466
497
|
replyToMessageId: number;
|
|
467
498
|
controlType: PendingTelegramControlItem<TContext>["controlType"];
|
|
468
499
|
statusSummary: string;
|
|
@@ -647,10 +678,10 @@ export function createTelegramAgentStartHook<
|
|
|
647
678
|
TTurn extends PendingTelegramTurn,
|
|
648
679
|
TContext = unknown,
|
|
649
680
|
>(deps: TelegramAgentStartHookRuntimeDeps<TTurn, TContext>) {
|
|
650
|
-
return async
|
|
681
|
+
return async (
|
|
651
682
|
_event: TelegramAgentStartHookEvent,
|
|
652
683
|
ctx: TContext,
|
|
653
|
-
): Promise<void> {
|
|
684
|
+
): Promise<void> => {
|
|
654
685
|
deps.setAbortHandler(ctx);
|
|
655
686
|
handleTelegramAgentStartRuntime<TTurn, TContext>({
|
|
656
687
|
queuedItems: deps.getQueuedItems(),
|
|
@@ -806,24 +837,29 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
806
837
|
waitForTypingIdle?: () => Promise<void>;
|
|
807
838
|
updateStatus: () => void;
|
|
808
839
|
dispatchNextQueuedTelegramTurn: () => void;
|
|
809
|
-
|
|
840
|
+
scheduleActiveTurnDelivery?: (task: () => Promise<void>) => void;
|
|
841
|
+
clearPreview: (
|
|
842
|
+
chatId: number,
|
|
843
|
+
options?: { target?: TelegramQueueTarget },
|
|
844
|
+
) => Promise<void>;
|
|
810
845
|
setPreviewPendingText: (text: string) => void;
|
|
811
846
|
finalizeMarkdownPreview: (
|
|
812
847
|
chatId: number,
|
|
813
848
|
markdown: string,
|
|
814
849
|
replyToMessageId: number,
|
|
815
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
850
|
+
options?: { replyMarkup?: TReplyMarkup; target?: TelegramQueueTarget },
|
|
816
851
|
) => Promise<boolean>;
|
|
817
852
|
sendMarkdownReply: (
|
|
818
853
|
chatId: number,
|
|
819
854
|
replyToMessageId: number | undefined,
|
|
820
855
|
markdown: string,
|
|
821
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
856
|
+
options?: { replyMarkup?: TReplyMarkup; target?: TelegramQueueTarget },
|
|
822
857
|
) => Promise<unknown>;
|
|
823
858
|
sendTextReply: (
|
|
824
859
|
chatId: number,
|
|
825
860
|
replyToMessageId: number,
|
|
826
861
|
text: string,
|
|
862
|
+
options?: { target?: TelegramQueueTarget },
|
|
827
863
|
) => Promise<unknown>;
|
|
828
864
|
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
829
865
|
answerGuestQuery?: (
|
|
@@ -841,6 +877,7 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
841
877
|
options?: { replyToPrompt?: boolean },
|
|
842
878
|
) => Promise<void>;
|
|
843
879
|
getDefaultChatId?: () => number | undefined;
|
|
880
|
+
getDefaultTarget?: () => TelegramQueueTarget | undefined;
|
|
844
881
|
isProactivePushEnabled?: () => boolean;
|
|
845
882
|
canSendProactivePush?: () => boolean;
|
|
846
883
|
recordRuntimeEvent?: (
|
|
@@ -857,6 +894,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
857
894
|
TReplyMarkup = unknown,
|
|
858
895
|
> {
|
|
859
896
|
getActiveTurn: () => TTurn | undefined;
|
|
897
|
+
loadConfig?: () => Promise<void>;
|
|
860
898
|
extractAssistant: (
|
|
861
899
|
messages: readonly TMessage[],
|
|
862
900
|
) => TelegramAgentEndAssistantResult;
|
|
@@ -868,7 +906,14 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
868
906
|
requestDeferredDispatchNextQueuedTelegramTurn: (
|
|
869
907
|
dispatch: (ctx: TContext) => void,
|
|
870
908
|
) => void;
|
|
871
|
-
|
|
909
|
+
scheduleActiveTurnDelivery?: TelegramAgentEndRuntimeDeps<
|
|
910
|
+
TTurn,
|
|
911
|
+
TReplyMarkup
|
|
912
|
+
>["scheduleActiveTurnDelivery"];
|
|
913
|
+
clearPreview: TelegramAgentEndRuntimeDeps<
|
|
914
|
+
TTurn,
|
|
915
|
+
TReplyMarkup
|
|
916
|
+
>["clearPreview"];
|
|
872
917
|
setPreviewPendingText: (text: string) => void;
|
|
873
918
|
finalizeMarkdownPreview: TelegramAgentEndRuntimeDeps<
|
|
874
919
|
TTurn,
|
|
@@ -888,6 +933,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
888
933
|
>["planOutboundReply"];
|
|
889
934
|
sendOutboundReplyArtifacts?: TelegramAgentEndRuntimeDeps<TTurn>["sendOutboundReplyArtifacts"];
|
|
890
935
|
getDefaultChatId?: TelegramAgentEndRuntimeDeps<TTurn>["getDefaultChatId"];
|
|
936
|
+
getDefaultTarget?: TelegramAgentEndRuntimeDeps<TTurn>["getDefaultTarget"];
|
|
891
937
|
isProactivePushEnabled?: TelegramAgentEndRuntimeDeps<TTurn>["isProactivePushEnabled"];
|
|
892
938
|
canSendProactivePush?: (ctx: TContext) => boolean;
|
|
893
939
|
recordRuntimeEvent?: TelegramAgentEndRuntimeDeps<TTurn>["recordRuntimeEvent"];
|
|
@@ -976,10 +1022,11 @@ export function createTelegramAgentEndHook<
|
|
|
976
1022
|
TReplyMarkup
|
|
977
1023
|
>,
|
|
978
1024
|
) {
|
|
979
|
-
return async
|
|
1025
|
+
return async (
|
|
980
1026
|
event: TelegramAgentEndHookEvent<TMessage>,
|
|
981
1027
|
ctx: TContext,
|
|
982
|
-
): Promise<void> {
|
|
1028
|
+
): Promise<void> => {
|
|
1029
|
+
await deps.loadConfig?.();
|
|
983
1030
|
const turn = deps.getActiveTurn();
|
|
984
1031
|
const proactiveEnabled = deps.isProactivePushEnabled?.() ?? false;
|
|
985
1032
|
const canProactivePush = deps.canSendProactivePush?.(ctx) ?? false;
|
|
@@ -996,6 +1043,7 @@ export function createTelegramAgentEndHook<
|
|
|
996
1043
|
deps.dispatchNextQueuedTelegramTurn,
|
|
997
1044
|
);
|
|
998
1045
|
},
|
|
1046
|
+
scheduleActiveTurnDelivery: deps.scheduleActiveTurnDelivery,
|
|
999
1047
|
clearPreview: deps.clearPreview,
|
|
1000
1048
|
setPreviewPendingText: deps.setPreviewPendingText,
|
|
1001
1049
|
finalizeMarkdownPreview: deps.finalizeMarkdownPreview,
|
|
@@ -1007,6 +1055,7 @@ export function createTelegramAgentEndHook<
|
|
|
1007
1055
|
planOutboundReply: deps.planOutboundReply,
|
|
1008
1056
|
sendOutboundReplyArtifacts: deps.sendOutboundReplyArtifacts,
|
|
1009
1057
|
getDefaultChatId: deps.getDefaultChatId,
|
|
1058
|
+
getDefaultTarget: deps.getDefaultTarget,
|
|
1010
1059
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
1011
1060
|
canSendProactivePush: () => canProactivePush,
|
|
1012
1061
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
@@ -1065,20 +1114,26 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1065
1114
|
const canProactivePush = deps.canSendProactivePush?.() ?? false;
|
|
1066
1115
|
if (proactiveEnabled && finalText && !assistant.errorMessage) {
|
|
1067
1116
|
if (canProactivePush) {
|
|
1068
|
-
const
|
|
1117
|
+
const defaultTarget = deps.getDefaultTarget?.();
|
|
1118
|
+
const defaultChatId = defaultTarget?.chatId ?? deps.getDefaultChatId?.();
|
|
1069
1119
|
if (defaultChatId !== undefined) {
|
|
1070
1120
|
try {
|
|
1071
|
-
await deps.sendMarkdownReply(defaultChatId, undefined, finalText
|
|
1121
|
+
await deps.sendMarkdownReply(defaultChatId, undefined, finalText, {
|
|
1122
|
+
target: defaultTarget,
|
|
1123
|
+
});
|
|
1072
1124
|
} catch (error) {
|
|
1073
1125
|
deps.recordRuntimeEvent?.("proactive-push", error, {
|
|
1074
1126
|
chatId: defaultChatId,
|
|
1127
|
+
threadId: defaultTarget?.threadId,
|
|
1075
1128
|
});
|
|
1076
1129
|
}
|
|
1077
1130
|
}
|
|
1078
1131
|
} else {
|
|
1079
1132
|
deps.recordRuntimeEvent?.(
|
|
1080
1133
|
"proactive-push",
|
|
1081
|
-
new Error(
|
|
1134
|
+
new Error(
|
|
1135
|
+
"Proactive push skipped because this instance does not own Telegram polling.",
|
|
1136
|
+
),
|
|
1082
1137
|
{ phase: "ownership" },
|
|
1083
1138
|
);
|
|
1084
1139
|
}
|
|
@@ -1090,7 +1145,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1090
1145
|
if (assistant.errorMessage) {
|
|
1091
1146
|
await deps.answerGuestQuery?.(
|
|
1092
1147
|
turn.guestQueryId,
|
|
1093
|
-
"Telegram bridge:
|
|
1148
|
+
"Telegram bridge: Pi failed while processing the request.",
|
|
1094
1149
|
);
|
|
1095
1150
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1096
1151
|
return;
|
|
@@ -1106,86 +1161,99 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1106
1161
|
return;
|
|
1107
1162
|
}
|
|
1108
1163
|
if (endPlan.shouldClearPreview) {
|
|
1109
|
-
await deps.clearPreview(turn.chatId);
|
|
1164
|
+
await deps.clearPreview(turn.chatId, { target: turn.target });
|
|
1110
1165
|
}
|
|
1111
1166
|
if (endPlan.shouldSendErrorMessage) {
|
|
1112
1167
|
await deps.sendTextReply(
|
|
1113
1168
|
turn.chatId,
|
|
1114
1169
|
turn.replyToMessageId,
|
|
1115
1170
|
assistant.errorMessage ||
|
|
1116
|
-
"Telegram bridge:
|
|
1171
|
+
"Telegram bridge: Pi failed while processing the request.",
|
|
1172
|
+
{ target: turn.target },
|
|
1117
1173
|
);
|
|
1118
1174
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1119
1175
|
return;
|
|
1120
1176
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
turn.replyToMessageId,
|
|
1129
|
-
{ replyMarkup },
|
|
1130
|
-
);
|
|
1131
|
-
if (!finalized) {
|
|
1132
|
-
await deps.clearPreview(turn.chatId);
|
|
1133
|
-
await deps.sendMarkdownReply(
|
|
1177
|
+
const deliverActiveTurn = async () => {
|
|
1178
|
+
if (finalText) deps.setPreviewPendingText(finalText);
|
|
1179
|
+
if (!finalText && hasOutboundArtifacts)
|
|
1180
|
+
await deps.clearPreview(turn.chatId, { target: turn.target });
|
|
1181
|
+
if (endPlan.kind === "text" && finalText) {
|
|
1182
|
+
try {
|
|
1183
|
+
const finalized = await deps.finalizeMarkdownPreview(
|
|
1134
1184
|
turn.chatId,
|
|
1135
|
-
turn.replyToMessageId,
|
|
1136
1185
|
finalText,
|
|
1137
|
-
|
|
1186
|
+
turn.replyToMessageId,
|
|
1187
|
+
{ replyMarkup, target: turn.target },
|
|
1138
1188
|
);
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
deps.recordRuntimeEvent?.("delivery", error, {
|
|
1142
|
-
phase: "final-text",
|
|
1143
|
-
chatId: turn.chatId,
|
|
1144
|
-
replyToMessageId: turn.replyToMessageId,
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
if (outboundReply && deps.sendOutboundReplyArtifacts) {
|
|
1149
|
-
try {
|
|
1150
|
-
await deps.sendOutboundReplyArtifacts(turn, outboundReply, {
|
|
1151
|
-
replyToPrompt: !finalText,
|
|
1152
|
-
});
|
|
1153
|
-
} catch (error) {
|
|
1154
|
-
deps.recordRuntimeEvent?.("delivery", error, {
|
|
1155
|
-
phase: "voice-artifacts",
|
|
1156
|
-
chatId: turn.chatId,
|
|
1157
|
-
});
|
|
1158
|
-
// Fallback to planned text when voice delivery fails and text wasn't already delivered
|
|
1159
|
-
if (rawFinalText?.trim() && !finalText && hasOutboundArtifacts) {
|
|
1160
|
-
try {
|
|
1161
|
-
const fallbackMarkdown =
|
|
1162
|
-
plannedReply?.markdown || outboundReply?.voiceText || rawFinalText;
|
|
1189
|
+
if (!finalized) {
|
|
1190
|
+
await deps.clearPreview(turn.chatId, { target: turn.target });
|
|
1163
1191
|
await deps.sendMarkdownReply(
|
|
1164
1192
|
turn.chatId,
|
|
1165
1193
|
turn.replyToMessageId,
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
? { replyMarkup: plannedReply.replyMarkup }
|
|
1169
|
-
: undefined,
|
|
1194
|
+
finalText,
|
|
1195
|
+
{ replyMarkup, target: turn.target },
|
|
1170
1196
|
);
|
|
1171
|
-
} catch (fallbackError) {
|
|
1172
|
-
deps.recordRuntimeEvent?.("delivery", fallbackError, {
|
|
1173
|
-
phase: "voice-fallback-text",
|
|
1174
|
-
chatId: turn.chatId,
|
|
1175
|
-
});
|
|
1176
1197
|
}
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
deps.recordRuntimeEvent?.("delivery", error, {
|
|
1200
|
+
phase: "final-text",
|
|
1201
|
+
chatId: turn.chatId,
|
|
1202
|
+
replyToMessageId: turn.replyToMessageId,
|
|
1203
|
+
});
|
|
1177
1204
|
}
|
|
1178
1205
|
}
|
|
1206
|
+
if (outboundReply && deps.sendOutboundReplyArtifacts) {
|
|
1207
|
+
try {
|
|
1208
|
+
await deps.sendOutboundReplyArtifacts(turn, outboundReply, {
|
|
1209
|
+
replyToPrompt: !finalText,
|
|
1210
|
+
});
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
deps.recordRuntimeEvent?.("delivery", error, {
|
|
1213
|
+
phase: "voice-artifacts",
|
|
1214
|
+
chatId: turn.chatId,
|
|
1215
|
+
});
|
|
1216
|
+
// Fallback to planned text when voice delivery fails and text wasn't already delivered
|
|
1217
|
+
if (rawFinalText?.trim() && !finalText && hasOutboundArtifacts) {
|
|
1218
|
+
try {
|
|
1219
|
+
const fallbackMarkdown =
|
|
1220
|
+
plannedReply?.markdown || outboundReply?.voiceText || rawFinalText;
|
|
1221
|
+
await deps.sendMarkdownReply(
|
|
1222
|
+
turn.chatId,
|
|
1223
|
+
turn.replyToMessageId,
|
|
1224
|
+
fallbackMarkdown,
|
|
1225
|
+
plannedReply?.replyMarkup || turn.target
|
|
1226
|
+
? { replyMarkup: plannedReply?.replyMarkup, target: turn.target }
|
|
1227
|
+
: undefined,
|
|
1228
|
+
);
|
|
1229
|
+
} catch (fallbackError) {
|
|
1230
|
+
deps.recordRuntimeEvent?.("delivery", fallbackError, {
|
|
1231
|
+
phase: "voice-fallback-text",
|
|
1232
|
+
chatId: turn.chatId,
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (endPlan.shouldSendAttachmentNotice) {
|
|
1239
|
+
await deps.sendTextReply(
|
|
1240
|
+
turn.chatId,
|
|
1241
|
+
turn.replyToMessageId,
|
|
1242
|
+
"Attached requested file(s).",
|
|
1243
|
+
{ target: turn.target },
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
await deps.sendQueuedAttachments(turn);
|
|
1247
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1248
|
+
};
|
|
1249
|
+
if (
|
|
1250
|
+
deps.scheduleActiveTurnDelivery &&
|
|
1251
|
+
(endPlan.kind === "text" || endPlan.kind === "attachments-only")
|
|
1252
|
+
) {
|
|
1253
|
+
deps.scheduleActiveTurnDelivery(deliverActiveTurn);
|
|
1254
|
+
return;
|
|
1179
1255
|
}
|
|
1180
|
-
|
|
1181
|
-
await deps.sendTextReply(
|
|
1182
|
-
turn.chatId,
|
|
1183
|
-
turn.replyToMessageId,
|
|
1184
|
-
"Attached requested file(s).",
|
|
1185
|
-
);
|
|
1186
|
-
}
|
|
1187
|
-
await deps.sendQueuedAttachments(turn);
|
|
1188
|
-
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1256
|
+
await deliverActiveTurn();
|
|
1189
1257
|
}
|
|
1190
1258
|
|
|
1191
1259
|
// --- Session Runtime ---
|
|
@@ -1255,7 +1323,11 @@ export interface TelegramSessionShutdownRuntimeDeps<TQueueItem> {
|
|
|
1255
1323
|
clearPendingMediaGroups: () => void;
|
|
1256
1324
|
clearModelMenuState: () => void;
|
|
1257
1325
|
getActiveTurnChatId: () => number | undefined;
|
|
1258
|
-
|
|
1326
|
+
getActiveTurnTarget?: () => TelegramQueueTarget | undefined;
|
|
1327
|
+
clearPreview: (
|
|
1328
|
+
chatId: number,
|
|
1329
|
+
options?: { target?: TelegramQueueTarget },
|
|
1330
|
+
) => Promise<void>;
|
|
1259
1331
|
clearActiveTurn: () => void;
|
|
1260
1332
|
clearAbort: () => void;
|
|
1261
1333
|
stopPolling: () => Promise<void>;
|
|
@@ -1279,7 +1351,11 @@ export interface TelegramSessionLifecycleHookRuntimeDeps<
|
|
|
1279
1351
|
clearPendingMediaGroups: () => void;
|
|
1280
1352
|
clearModelMenuState: () => void;
|
|
1281
1353
|
getActiveTurnChatId: () => number | undefined;
|
|
1282
|
-
|
|
1354
|
+
getActiveTurnTarget?: () => TelegramQueueTarget | undefined;
|
|
1355
|
+
clearPreview: (
|
|
1356
|
+
chatId: number,
|
|
1357
|
+
options?: { target?: TelegramQueueTarget },
|
|
1358
|
+
) => Promise<void>;
|
|
1283
1359
|
clearActiveTurn: () => void;
|
|
1284
1360
|
clearAbort: () => void;
|
|
1285
1361
|
stopPolling: () => Promise<void>;
|
|
@@ -1328,12 +1404,21 @@ export interface TelegramQueueMutationController<TContext> {
|
|
|
1328
1404
|
append: (item: TelegramQueueItem<TContext>, ctx: TContext) => void;
|
|
1329
1405
|
reorder: (ctx: TContext) => void;
|
|
1330
1406
|
clear: (ctx: TContext) => number;
|
|
1331
|
-
removeByMessageIds: (
|
|
1332
|
-
|
|
1407
|
+
removeByMessageIds: (
|
|
1408
|
+
messageIds: number[],
|
|
1409
|
+
ctx: TContext,
|
|
1410
|
+
scope?: TelegramQueueMessageScope,
|
|
1411
|
+
) => number;
|
|
1412
|
+
clearPriorityByMessageId: (
|
|
1413
|
+
messageId: number,
|
|
1414
|
+
ctx: TContext,
|
|
1415
|
+
scope?: TelegramQueueMessageScope,
|
|
1416
|
+
) => boolean;
|
|
1333
1417
|
prioritizeByMessageId: (
|
|
1334
1418
|
messageId: number,
|
|
1335
1419
|
ctx: TContext,
|
|
1336
1420
|
priorityEmoji?: string,
|
|
1421
|
+
scope?: TelegramQueueMessageScope,
|
|
1337
1422
|
) => boolean;
|
|
1338
1423
|
}
|
|
1339
1424
|
|
|
@@ -1445,7 +1530,8 @@ export async function shutdownTelegramSessionRuntime<TQueueItem>(
|
|
|
1445
1530
|
deps.clearModelMenuState();
|
|
1446
1531
|
const activeTurnChatId = deps.getActiveTurnChatId();
|
|
1447
1532
|
if (activeTurnChatId !== undefined) {
|
|
1448
|
-
|
|
1533
|
+
const target = deps.getActiveTurnTarget?.();
|
|
1534
|
+
await deps.clearPreview(activeTurnChatId, target ? { target } : undefined);
|
|
1449
1535
|
}
|
|
1450
1536
|
deps.clearActiveTurn();
|
|
1451
1537
|
deps.clearAbort();
|
|
@@ -1485,6 +1571,7 @@ export function createTelegramSessionLifecycleRuntime<
|
|
|
1485
1571
|
clearPendingMediaGroups: deps.clearPendingMediaGroups,
|
|
1486
1572
|
clearModelMenuState: deps.clearModelMenuState,
|
|
1487
1573
|
getActiveTurnChatId: deps.getActiveTurnChatId,
|
|
1574
|
+
getActiveTurnTarget: deps.getActiveTurnTarget,
|
|
1488
1575
|
clearPreview: deps.clearPreview,
|
|
1489
1576
|
clearActiveTurn: deps.clearActiveTurn,
|
|
1490
1577
|
clearAbort: deps.clearAbort,
|
|
@@ -1526,6 +1613,7 @@ export function createTelegramSessionLifecycleHooks<
|
|
|
1526
1613
|
clearPendingMediaGroups: deps.clearPendingMediaGroups,
|
|
1527
1614
|
clearModelMenuState: deps.clearModelMenuState,
|
|
1528
1615
|
getActiveTurnChatId: deps.getActiveTurnChatId,
|
|
1616
|
+
getActiveTurnTarget: deps.getActiveTurnTarget,
|
|
1529
1617
|
clearPreview: deps.clearPreview,
|
|
1530
1618
|
clearActiveTurn: deps.clearActiveTurn,
|
|
1531
1619
|
clearAbort: deps.clearAbort,
|
|
@@ -1553,18 +1641,24 @@ export function createTelegramQueueMutationController<TContext>(
|
|
|
1553
1641
|
appendTelegramQueueItemRuntime(item, buildRuntimeDeps(ctx)),
|
|
1554
1642
|
reorder: (ctx) => reorderTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
|
|
1555
1643
|
clear: (ctx) => clearTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
|
|
1556
|
-
removeByMessageIds: (messageIds, ctx) =>
|
|
1644
|
+
removeByMessageIds: (messageIds, ctx, scope) =>
|
|
1557
1645
|
removeTelegramQueueItemsByMessageIdsRuntime(
|
|
1558
1646
|
messageIds,
|
|
1559
1647
|
buildRuntimeDeps(ctx),
|
|
1648
|
+
scope,
|
|
1649
|
+
),
|
|
1650
|
+
clearPriorityByMessageId: (messageId, ctx, scope) =>
|
|
1651
|
+
clearTelegramQueuePromptPriorityRuntime(
|
|
1652
|
+
messageId,
|
|
1653
|
+
buildRuntimeDeps(ctx),
|
|
1654
|
+
scope,
|
|
1560
1655
|
),
|
|
1561
|
-
|
|
1562
|
-
clearTelegramQueuePromptPriorityRuntime(messageId, buildRuntimeDeps(ctx)),
|
|
1563
|
-
prioritizeByMessageId: (messageId, ctx, priorityEmoji) =>
|
|
1656
|
+
prioritizeByMessageId: (messageId, ctx, priorityEmoji, scope) =>
|
|
1564
1657
|
prioritizeTelegramQueuePromptRuntime(
|
|
1565
1658
|
messageId,
|
|
1566
1659
|
buildRuntimeDeps(ctx),
|
|
1567
1660
|
priorityEmoji,
|
|
1661
|
+
scope,
|
|
1568
1662
|
),
|
|
1569
1663
|
};
|
|
1570
1664
|
}
|
|
@@ -1607,10 +1701,12 @@ export function clearTelegramQueueItemsRuntime<TContext>(
|
|
|
1607
1701
|
export function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(
|
|
1608
1702
|
messageIds: number[],
|
|
1609
1703
|
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1704
|
+
scope?: TelegramQueueMessageScope,
|
|
1610
1705
|
): number {
|
|
1611
1706
|
const { items, removedCount } = removeTelegramQueueItemsByMessageIds(
|
|
1612
1707
|
deps.getQueuedItems(),
|
|
1613
1708
|
messageIds,
|
|
1709
|
+
scope,
|
|
1614
1710
|
);
|
|
1615
1711
|
if (removedCount === 0) return 0;
|
|
1616
1712
|
deps.setQueuedItems(items);
|
|
@@ -1625,10 +1721,12 @@ export function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(
|
|
|
1625
1721
|
export function clearTelegramQueuePromptPriorityRuntime<TContext>(
|
|
1626
1722
|
messageId: number,
|
|
1627
1723
|
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1724
|
+
scope?: TelegramQueueMessageScope,
|
|
1628
1725
|
): boolean {
|
|
1629
1726
|
const { changed, items } = clearTelegramQueuePromptPriority(
|
|
1630
1727
|
deps.getQueuedItems(),
|
|
1631
1728
|
messageId,
|
|
1729
|
+
scope,
|
|
1632
1730
|
);
|
|
1633
1731
|
if (!changed) return false;
|
|
1634
1732
|
deps.setQueuedItems(items);
|
|
@@ -1640,6 +1738,7 @@ export function prioritizeTelegramQueuePromptRuntime<TContext>(
|
|
|
1640
1738
|
messageId: number,
|
|
1641
1739
|
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1642
1740
|
priorityEmoji?: string,
|
|
1741
|
+
scope?: TelegramQueueMessageScope,
|
|
1643
1742
|
): boolean {
|
|
1644
1743
|
const nextPriorityReactionOrder = deps.getNextPriorityReactionOrder?.();
|
|
1645
1744
|
if (nextPriorityReactionOrder === undefined) return false;
|
|
@@ -1648,6 +1747,7 @@ export function prioritizeTelegramQueuePromptRuntime<TContext>(
|
|
|
1648
1747
|
messageId,
|
|
1649
1748
|
nextPriorityReactionOrder,
|
|
1650
1749
|
priorityEmoji,
|
|
1750
|
+
scope,
|
|
1651
1751
|
);
|
|
1652
1752
|
if (!changed) return false;
|
|
1653
1753
|
deps.setQueuedItems(items);
|
|
@@ -1728,6 +1828,7 @@ export interface TelegramControlRuntimeDeps<
|
|
|
1728
1828
|
chatId: number,
|
|
1729
1829
|
replyToMessageId: number,
|
|
1730
1830
|
text: string,
|
|
1831
|
+
options?: { target?: TelegramQueueTarget },
|
|
1731
1832
|
) => Promise<number | undefined>;
|
|
1732
1833
|
onSettled: () => void;
|
|
1733
1834
|
}
|
|
@@ -1749,6 +1850,7 @@ export async function executeTelegramControlItemRuntime<TContext>(
|
|
|
1749
1850
|
item.chatId,
|
|
1750
1851
|
item.replyToMessageId,
|
|
1751
1852
|
`Telegram control action failed: ${message}`,
|
|
1853
|
+
{ target: item.target },
|
|
1752
1854
|
);
|
|
1753
1855
|
} finally {
|
|
1754
1856
|
deps.onSettled();
|
package/lib/rendering.ts
CHANGED
|
@@ -463,31 +463,6 @@ function matchMarkdownHeadingLine(line: string): RegExpMatchArray | null {
|
|
|
463
463
|
return line.match(/^(\s*)#{1,6}\s+(.+)$/);
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
function endsWithMarkdownHeadingLine(markdown: string): boolean {
|
|
467
|
-
const lines = markdown.split("\n");
|
|
468
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
469
|
-
const line = lines[index] ?? "";
|
|
470
|
-
if (line.trim().length === 0) continue;
|
|
471
|
-
return matchMarkdownHeadingLine(line) !== null;
|
|
472
|
-
}
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function splitLeadingMarkdownBlankLines(markdown: string): {
|
|
477
|
-
blankLines: number;
|
|
478
|
-
remainingText: string;
|
|
479
|
-
} {
|
|
480
|
-
const lines = markdown.split("\n");
|
|
481
|
-
let start = 0;
|
|
482
|
-
while (start < lines.length && (lines[start] ?? "").trim().length === 0) {
|
|
483
|
-
start += 1;
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
blankLines: start,
|
|
487
|
-
remainingText: lines.slice(start).join("\n"),
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
466
|
// --- UI Markdown-to-Telegram-HTML Rendering ---
|
|
492
467
|
|
|
493
468
|
function renderDelimitedInlineStyle(
|