@llblab/pi-telegram 0.6.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +153 -0
- package/BACKLOG.md +5 -0
- package/CHANGELOG.md +148 -0
- package/README.md +40 -39
- package/docs/README.md +1 -0
- package/docs/architecture.md +36 -27
- package/docs/attachment-handlers.md +4 -6
- package/docs/callback-namespaces.md +36 -0
- package/docs/command-templates.md +53 -9
- package/docs/locks.md +4 -0
- package/docs/outbound-handlers.md +6 -5
- package/index.ts +60 -7
- package/lib/api.ts +1 -0
- package/lib/attachment-handlers.ts +21 -10
- package/lib/attachments.ts +1 -0
- package/lib/command-templates.ts +37 -3
- package/lib/commands.ts +363 -88
- package/lib/config.ts +6 -2
- package/lib/keyboard.ts +14 -0
- package/lib/lifecycle.ts +26 -0
- package/lib/locks.ts +3 -2
- package/lib/media.ts +1 -0
- package/lib/menu-model.ts +881 -0
- package/lib/menu-queue.ts +610 -0
- package/lib/menu-status.ts +226 -0
- package/lib/menu-thinking.ts +171 -0
- package/lib/menu.ts +143 -1019
- package/lib/model.ts +1 -0
- package/lib/outbound-handlers.ts +28 -19
- package/lib/pi.ts +8 -0
- package/lib/polling.ts +1 -0
- package/lib/preview.ts +97 -50
- package/lib/prompt-templates.ts +150 -0
- package/lib/prompts.ts +2 -0
- package/lib/queue.ts +60 -15
- package/lib/rendering.ts +1 -0
- package/lib/replies.ts +90 -2
- package/lib/routing.ts +106 -14
- package/lib/runtime.ts +2 -0
- package/lib/setup.ts +1 -0
- package/lib/status.ts +18 -6
- package/lib/turns.ts +1 -0
- package/lib/updates.ts +55 -6
- package/package.json +5 -2
package/lib/queue.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram queue and
|
|
3
|
-
*
|
|
2
|
+
* Telegram queue core contracts and pure planning helpers
|
|
3
|
+
* Zones: telegram queue, pi agent lifecycle, scheduling
|
|
4
|
+
* Owns queue item contracts, lane admission, pure queue mutations, and dispatch planning
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
// --- Queue Items ---
|
|
@@ -178,6 +179,14 @@ export function createTelegramQueueStore<TContext = unknown>(
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
export function createTelegramQueueItemCountGetter<TContext = unknown>(
|
|
183
|
+
store: Pick<TelegramQueueStore<TContext>, "getQueuedItems">,
|
|
184
|
+
): () => number {
|
|
185
|
+
return function getTelegramQueueItemCount() {
|
|
186
|
+
return store.getQueuedItems().length;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
181
190
|
export function createTelegramActiveTurnStore<
|
|
182
191
|
TTurn extends PendingTelegramTurn = PendingTelegramTurn,
|
|
183
192
|
>(): TelegramActiveTurnStore<TTurn> {
|
|
@@ -344,7 +353,7 @@ function formatTelegramQueueItemStatusSummary<TContext = unknown>(
|
|
|
344
353
|
item: TelegramQueueItem<TContext>,
|
|
345
354
|
): string {
|
|
346
355
|
if (item.queueLane === "priority") {
|
|
347
|
-
return
|
|
356
|
+
return `⚡ ${item.statusSummary}`;
|
|
348
357
|
}
|
|
349
358
|
return item.statusSummary;
|
|
350
359
|
}
|
|
@@ -665,18 +674,32 @@ export type TelegramAgentLifecycleHooksRuntimeDeps<
|
|
|
665
674
|
TTurn extends PendingTelegramTurn,
|
|
666
675
|
TContext,
|
|
667
676
|
TMessage,
|
|
677
|
+
TReplyMarkup = unknown,
|
|
668
678
|
> = TelegramAgentStartHookRuntimeDeps<TTurn, TContext> &
|
|
669
|
-
TelegramAgentEndHookRuntimeDeps<TTurn, TContext, TMessage> &
|
|
679
|
+
TelegramAgentEndHookRuntimeDeps<TTurn, TContext, TMessage, TReplyMarkup> &
|
|
670
680
|
TelegramToolExecutionHookRuntimeDeps<TContext>;
|
|
671
681
|
|
|
672
682
|
export function createTelegramAgentLifecycleHooks<
|
|
673
683
|
TTurn extends PendingTelegramTurn,
|
|
674
684
|
TContext,
|
|
675
685
|
TMessage,
|
|
676
|
-
|
|
686
|
+
TReplyMarkup = unknown,
|
|
687
|
+
>(
|
|
688
|
+
deps: TelegramAgentLifecycleHooksRuntimeDeps<
|
|
689
|
+
TTurn,
|
|
690
|
+
TContext,
|
|
691
|
+
TMessage,
|
|
692
|
+
TReplyMarkup
|
|
693
|
+
>,
|
|
694
|
+
) {
|
|
677
695
|
return {
|
|
678
696
|
onAgentStart: createTelegramAgentStartHook<TTurn, TContext>(deps),
|
|
679
|
-
onAgentEnd: createTelegramAgentEndHook<
|
|
697
|
+
onAgentEnd: createTelegramAgentEndHook<
|
|
698
|
+
TTurn,
|
|
699
|
+
TContext,
|
|
700
|
+
TMessage,
|
|
701
|
+
TReplyMarkup
|
|
702
|
+
>(deps),
|
|
680
703
|
...createTelegramToolExecutionHooks<TContext>(deps),
|
|
681
704
|
};
|
|
682
705
|
}
|
|
@@ -737,6 +760,7 @@ export interface TelegramAgentEndOutboundReplyPlan<TReplyMarkup = unknown> {
|
|
|
737
760
|
|
|
738
761
|
export interface TelegramAgentEndRuntimeDeps<
|
|
739
762
|
TTurn extends PendingTelegramTurn,
|
|
763
|
+
TReplyMarkup = unknown,
|
|
740
764
|
> {
|
|
741
765
|
turn: TTurn | undefined;
|
|
742
766
|
assistant: TelegramAgentEndAssistantResult;
|
|
@@ -750,13 +774,13 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
750
774
|
chatId: number,
|
|
751
775
|
markdown: string,
|
|
752
776
|
replyToMessageId: number,
|
|
753
|
-
options?: { replyMarkup?:
|
|
777
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
754
778
|
) => Promise<boolean>;
|
|
755
779
|
sendMarkdownReply: (
|
|
756
780
|
chatId: number,
|
|
757
781
|
replyToMessageId: number,
|
|
758
782
|
markdown: string,
|
|
759
|
-
options?: { replyMarkup?:
|
|
783
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
760
784
|
) => Promise<unknown>;
|
|
761
785
|
sendTextReply: (
|
|
762
786
|
chatId: number,
|
|
@@ -764,7 +788,9 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
764
788
|
text: string,
|
|
765
789
|
) => Promise<unknown>;
|
|
766
790
|
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
767
|
-
planOutboundReply?: (
|
|
791
|
+
planOutboundReply?: (
|
|
792
|
+
markdown: string,
|
|
793
|
+
) => TelegramAgentEndOutboundReplyPlan<TReplyMarkup>;
|
|
768
794
|
sendOutboundReplyArtifacts?: (
|
|
769
795
|
turn: TTurn,
|
|
770
796
|
plan: TelegramAgentEndOutboundReplyPlan,
|
|
@@ -776,6 +802,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
776
802
|
TTurn extends PendingTelegramTurn,
|
|
777
803
|
TContext,
|
|
778
804
|
TMessage,
|
|
805
|
+
TReplyMarkup = unknown,
|
|
779
806
|
> {
|
|
780
807
|
getActiveTurn: () => TTurn | undefined;
|
|
781
808
|
extractAssistant: (
|
|
@@ -790,11 +817,20 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
790
817
|
) => void;
|
|
791
818
|
clearPreview: (chatId: number) => Promise<void>;
|
|
792
819
|
setPreviewPendingText: (text: string) => void;
|
|
793
|
-
finalizeMarkdownPreview: TelegramAgentEndRuntimeDeps<
|
|
794
|
-
|
|
820
|
+
finalizeMarkdownPreview: TelegramAgentEndRuntimeDeps<
|
|
821
|
+
TTurn,
|
|
822
|
+
TReplyMarkup
|
|
823
|
+
>["finalizeMarkdownPreview"];
|
|
824
|
+
sendMarkdownReply: TelegramAgentEndRuntimeDeps<
|
|
825
|
+
TTurn,
|
|
826
|
+
TReplyMarkup
|
|
827
|
+
>["sendMarkdownReply"];
|
|
795
828
|
sendTextReply: TelegramAgentEndRuntimeDeps<TTurn>["sendTextReply"];
|
|
796
829
|
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
797
|
-
planOutboundReply?: TelegramAgentEndRuntimeDeps<
|
|
830
|
+
planOutboundReply?: TelegramAgentEndRuntimeDeps<
|
|
831
|
+
TTurn,
|
|
832
|
+
TReplyMarkup
|
|
833
|
+
>["planOutboundReply"];
|
|
798
834
|
sendOutboundReplyArtifacts?: TelegramAgentEndRuntimeDeps<TTurn>["sendOutboundReplyArtifacts"];
|
|
799
835
|
}
|
|
800
836
|
|
|
@@ -872,7 +908,15 @@ export function createTelegramAgentEndHook<
|
|
|
872
908
|
TTurn extends PendingTelegramTurn,
|
|
873
909
|
TContext,
|
|
874
910
|
TMessage,
|
|
875
|
-
|
|
911
|
+
TReplyMarkup = unknown,
|
|
912
|
+
>(
|
|
913
|
+
deps: TelegramAgentEndHookRuntimeDeps<
|
|
914
|
+
TTurn,
|
|
915
|
+
TContext,
|
|
916
|
+
TMessage,
|
|
917
|
+
TReplyMarkup
|
|
918
|
+
>,
|
|
919
|
+
) {
|
|
876
920
|
return async function onAgentEnd(
|
|
877
921
|
event: TelegramAgentEndHookEvent<TMessage>,
|
|
878
922
|
ctx: TContext,
|
|
@@ -903,7 +947,8 @@ export function createTelegramAgentEndHook<
|
|
|
903
947
|
|
|
904
948
|
export async function handleTelegramAgentEndRuntime<
|
|
905
949
|
TTurn extends PendingTelegramTurn,
|
|
906
|
-
|
|
950
|
+
TReplyMarkup = unknown,
|
|
951
|
+
>(deps: TelegramAgentEndRuntimeDeps<TTurn, TReplyMarkup>): Promise<void> {
|
|
907
952
|
const { turn, assistant } = deps;
|
|
908
953
|
const rawFinalText = assistant.text;
|
|
909
954
|
const outboundReply = rawFinalText
|
|
@@ -934,7 +979,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
934
979
|
turn.chatId,
|
|
935
980
|
turn.replyToMessageId,
|
|
936
981
|
assistant.errorMessage ||
|
|
937
|
-
"Telegram bridge:
|
|
982
|
+
"Telegram bridge: π failed while processing the request.",
|
|
938
983
|
);
|
|
939
984
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
940
985
|
return;
|
package/lib/rendering.ts
CHANGED
package/lib/replies.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram reply delivery helpers
|
|
3
|
+
* Zones: telegram outbound, rendering transport
|
|
3
4
|
* Owns rendered-message delivery, reply transport wiring, and plain or markdown final replies
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -10,10 +11,48 @@ import {
|
|
|
10
11
|
type TelegramRenderMode,
|
|
11
12
|
} from "./rendering.ts";
|
|
12
13
|
|
|
14
|
+
// --- Reply Dedup ---
|
|
15
|
+
|
|
16
|
+
/** Non-persistent reply deduplication for a single agent turn.
|
|
17
|
+
* First reply to a prompt gets `reply_parameters.reply_to_message_id`;
|
|
18
|
+
* subsequent replies in the same turn skip it to avoid stacking
|
|
19
|
+
* duplicate reply headers in the chat viewport. */
|
|
20
|
+
export interface ReplyDedupRuntime {
|
|
21
|
+
/** Returns true if this is the first reply for the given prompt
|
|
22
|
+
* message id in the current turn. Side-effect: marks it replied. */
|
|
23
|
+
shouldReply(promptMessageId: number): boolean;
|
|
24
|
+
/** Reset the tracker when a new prompt enters the queue. */
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createReplyDedupRuntime(): ReplyDedupRuntime {
|
|
29
|
+
const replied = new Map<number, boolean>();
|
|
30
|
+
return {
|
|
31
|
+
shouldReply(promptMessageId: number): boolean {
|
|
32
|
+
if (replied.has(promptMessageId)) return false;
|
|
33
|
+
replied.set(promptMessageId, true);
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
reset(): void {
|
|
37
|
+
replied.clear();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// --- Transport-level dedup ---
|
|
43
|
+
|
|
44
|
+
let lastRepliedToMessageId: number | undefined;
|
|
45
|
+
|
|
46
|
+
export function resetTransportReplyDedup(): void {
|
|
47
|
+
lastRepliedToMessageId = undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
13
50
|
export function buildTelegramReplyParameters(
|
|
14
51
|
messageId: number | undefined,
|
|
15
52
|
): TelegramReplyParameters | undefined {
|
|
16
53
|
if (messageId === undefined) return undefined;
|
|
54
|
+
if (messageId === lastRepliedToMessageId) return undefined;
|
|
55
|
+
lastRepliedToMessageId = messageId;
|
|
17
56
|
return { message_id: messageId, allow_sending_without_reply: true };
|
|
18
57
|
}
|
|
19
58
|
|
|
@@ -219,13 +258,13 @@ export interface TelegramRenderedMessageRuntimeDeps<TReplyMarkup> {
|
|
|
219
258
|
export interface TelegramRenderedMessageRuntime<TReplyMarkup> {
|
|
220
259
|
sendTextReply: (
|
|
221
260
|
chatId: number,
|
|
222
|
-
replyToMessageId: number,
|
|
261
|
+
replyToMessageId: number | undefined,
|
|
223
262
|
text: string,
|
|
224
263
|
options?: { parseMode?: "HTML" },
|
|
225
264
|
) => Promise<number | undefined>;
|
|
226
265
|
sendMarkdownReply: (
|
|
227
266
|
chatId: number,
|
|
228
|
-
replyToMessageId: number,
|
|
267
|
+
replyToMessageId: number | undefined,
|
|
229
268
|
markdown: string,
|
|
230
269
|
options?: { replyMarkup?: unknown },
|
|
231
270
|
) => Promise<number | undefined>;
|
|
@@ -332,3 +371,52 @@ export function createTelegramRenderedMessageRuntime<TReplyMarkup>(
|
|
|
332
371
|
},
|
|
333
372
|
};
|
|
334
373
|
}
|
|
374
|
+
|
|
375
|
+
// --- Dedup-wrapped Reply Wrappers ---
|
|
376
|
+
|
|
377
|
+
/** Wrap a sendTextReply with reply dedup so only the first message
|
|
378
|
+
* in a turn carries `reply_to_message_id`. */
|
|
379
|
+
export function dedupSendTextReply(
|
|
380
|
+
dedup: ReplyDedupRuntime,
|
|
381
|
+
inner: (
|
|
382
|
+
chatId: number,
|
|
383
|
+
replyToMessageId: number | undefined,
|
|
384
|
+
text: string,
|
|
385
|
+
options?: { parseMode?: "HTML" },
|
|
386
|
+
) => Promise<number | undefined>,
|
|
387
|
+
): (
|
|
388
|
+
chatId: number,
|
|
389
|
+
replyToMessageId: number,
|
|
390
|
+
text: string,
|
|
391
|
+
options?: { parseMode?: "HTML" },
|
|
392
|
+
) => Promise<number | undefined> {
|
|
393
|
+
return async (chatId, replyToMessageId, text, options) => {
|
|
394
|
+
const effectiveReplyTo = dedup.shouldReply(replyToMessageId)
|
|
395
|
+
? replyToMessageId
|
|
396
|
+
: undefined;
|
|
397
|
+
return inner(chatId, effectiveReplyTo, text, options);
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Wrap a sendMarkdownReply with reply dedup. */
|
|
402
|
+
export function dedupSendMarkdownReply<TReplyMarkup = unknown>(
|
|
403
|
+
dedup: ReplyDedupRuntime,
|
|
404
|
+
inner: (
|
|
405
|
+
chatId: number,
|
|
406
|
+
replyToMessageId: number | undefined,
|
|
407
|
+
markdown: string,
|
|
408
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
409
|
+
) => Promise<number | undefined>,
|
|
410
|
+
): (
|
|
411
|
+
chatId: number,
|
|
412
|
+
replyToMessageId: number,
|
|
413
|
+
markdown: string,
|
|
414
|
+
options?: { replyMarkup?: TReplyMarkup },
|
|
415
|
+
) => Promise<number | undefined> {
|
|
416
|
+
return async (chatId, replyToMessageId, markdown, options) => {
|
|
417
|
+
const effectiveReplyTo = dedup.shouldReply(replyToMessageId)
|
|
418
|
+
? replyToMessageId
|
|
419
|
+
: undefined;
|
|
420
|
+
return inner(chatId, effectiveReplyTo, markdown, options);
|
|
421
|
+
};
|
|
422
|
+
}
|
package/lib/routing.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram inbound routing composition
|
|
3
|
+
* Zones: telegram inbound, orchestration, queue/menu/command composition
|
|
3
4
|
* Wires authorized updates into menus, commands, media grouping, and prompt queueing
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ import * as Media from "./media.ts";
|
|
|
11
12
|
import * as Menu from "./menu.ts";
|
|
12
13
|
import * as Model from "./model.ts";
|
|
13
14
|
import * as Queue from "./queue.ts";
|
|
15
|
+
import * as PromptTemplates from "./prompt-templates.ts";
|
|
14
16
|
import type { TelegramBridgeRuntime } from "./runtime.ts";
|
|
15
17
|
import * as Turns from "./turns.ts";
|
|
16
18
|
import * as Updates from "./updates.ts";
|
|
@@ -25,11 +27,6 @@ export type TelegramRoutedCallbackQuery = Updates.TelegramCallbackQuery &
|
|
|
25
27
|
Menu.MenuCallbackQuery;
|
|
26
28
|
|
|
27
29
|
export interface TelegramInboundRouteRuntimeDeps<
|
|
28
|
-
TUpdate extends Updates.TelegramUpdateFlow & {
|
|
29
|
-
message?: TMessage;
|
|
30
|
-
edited_message?: TMessage;
|
|
31
|
-
callback_query?: TCallbackQuery;
|
|
32
|
-
},
|
|
33
30
|
TMessage extends TelegramRoutedMessage,
|
|
34
31
|
TCallbackQuery extends TelegramRoutedCallbackQuery,
|
|
35
32
|
TContext,
|
|
@@ -51,6 +48,15 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
51
48
|
Model.ScopedTelegramModel<TModel>
|
|
52
49
|
>;
|
|
53
50
|
menuActions: Menu.TelegramMenuActionRuntime<TContext, TModel>;
|
|
51
|
+
openQueueMenu: (
|
|
52
|
+
chatId: number,
|
|
53
|
+
replyToMessageId: number,
|
|
54
|
+
ctx: TContext,
|
|
55
|
+
) => Promise<void>;
|
|
56
|
+
queueMenuCallbackHandler: (
|
|
57
|
+
query: TCallbackQuery,
|
|
58
|
+
ctx: TContext,
|
|
59
|
+
) => Promise<boolean>;
|
|
54
60
|
buttonActionStore?: OutboundHandlers.TelegramButtonActionStore;
|
|
55
61
|
attachmentHandlerRuntime: TelegramAttachmentHandlerRuntime<TContext>;
|
|
56
62
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
@@ -65,10 +71,14 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
65
71
|
text: string,
|
|
66
72
|
) => Promise<number | undefined>;
|
|
67
73
|
setMyCommands: Commands.TelegramBotCommandRegistrationDeps["setMyCommands"];
|
|
74
|
+
getCommands: () => Parameters<
|
|
75
|
+
typeof PromptTemplates.getTelegramPromptTemplateCommands
|
|
76
|
+
>[0];
|
|
68
77
|
downloadFile: Media.DownloadTelegramMessageFilesDeps["downloadFile"];
|
|
69
78
|
getThinkingLevel: () => Model.ThinkingLevel;
|
|
70
79
|
setThinkingLevel: (level: Model.ThinkingLevel) => void;
|
|
71
80
|
setModel: (model: TModel) => Promise<boolean>;
|
|
81
|
+
sendUserMessage?: (message: string) => void;
|
|
72
82
|
isIdle: (ctx: TContext) => boolean;
|
|
73
83
|
hasPendingMessages: (ctx: TContext) => boolean;
|
|
74
84
|
compact: (
|
|
@@ -82,6 +92,21 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
82
92
|
) => void;
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
const TELEGRAM_OWNED_CALLBACK_PREFIXES = [
|
|
96
|
+
"menu:",
|
|
97
|
+
"model:",
|
|
98
|
+
"queue:",
|
|
99
|
+
"status:",
|
|
100
|
+
"tgbtn:",
|
|
101
|
+
"thinking:",
|
|
102
|
+
] as const;
|
|
103
|
+
|
|
104
|
+
function isTelegramOwnedCallbackData(data: string): boolean {
|
|
105
|
+
return TELEGRAM_OWNED_CALLBACK_PREFIXES.some((prefix) =>
|
|
106
|
+
data.startsWith(prefix),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
85
110
|
export function createTelegramInboundRouteRuntime<
|
|
86
111
|
TUpdate extends Updates.TelegramUpdateFlow & {
|
|
87
112
|
message?: TMessage;
|
|
@@ -94,7 +119,6 @@ export function createTelegramInboundRouteRuntime<
|
|
|
94
119
|
TModel extends Model.MenuModel,
|
|
95
120
|
>(
|
|
96
121
|
deps: TelegramInboundRouteRuntimeDeps<
|
|
97
|
-
TUpdate,
|
|
98
122
|
TMessage,
|
|
99
123
|
TCallbackQuery,
|
|
100
124
|
TContext,
|
|
@@ -159,8 +183,65 @@ export function createTelegramInboundRouteRuntime<
|
|
|
159
183
|
);
|
|
160
184
|
if (handled) return;
|
|
161
185
|
}
|
|
186
|
+
const handledByQueue = await deps.queueMenuCallbackHandler(query, ctx);
|
|
187
|
+
if (handledByQueue) return;
|
|
188
|
+
const callbackData = query.data;
|
|
189
|
+
if (
|
|
190
|
+
deps.sendUserMessage &&
|
|
191
|
+
callbackData &&
|
|
192
|
+
!isTelegramOwnedCallbackData(callbackData)
|
|
193
|
+
) {
|
|
194
|
+
deps.sendUserMessage(`[callback] ${callbackData}`);
|
|
195
|
+
await deps.answerCallbackQuery(query.id);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
162
198
|
await menuCallbackHandler(query, ctx);
|
|
163
199
|
};
|
|
200
|
+
const promptTurnBuilder = Turns.createTelegramPromptTurnRuntimeBuilder<
|
|
201
|
+
TMessage,
|
|
202
|
+
TContext
|
|
203
|
+
>({
|
|
204
|
+
allocateQueueOrder: deps.bridgeRuntime.queue.allocateItemOrder,
|
|
205
|
+
downloadFile: deps.downloadFile,
|
|
206
|
+
processAttachments: deps.attachmentHandlerRuntime.process,
|
|
207
|
+
});
|
|
208
|
+
const enqueueContinueTurn = async (
|
|
209
|
+
message: TMessage,
|
|
210
|
+
ctx: TContext,
|
|
211
|
+
): Promise<void> => {
|
|
212
|
+
const enqueuePlan = Queue.planTelegramPromptEnqueue(
|
|
213
|
+
deps.telegramQueueStore.getQueuedItems(),
|
|
214
|
+
deps.bridgeRuntime.lifecycle.shouldPreserveQueuedTurnsAsHistory(),
|
|
215
|
+
);
|
|
216
|
+
deps.bridgeRuntime.lifecycle.setPreserveQueuedTurnsAsHistory(false);
|
|
217
|
+
const continueMessage = {
|
|
218
|
+
...message,
|
|
219
|
+
text: "continue",
|
|
220
|
+
caption: undefined,
|
|
221
|
+
} as TMessage;
|
|
222
|
+
const turn = await promptTurnBuilder(
|
|
223
|
+
[continueMessage],
|
|
224
|
+
enqueuePlan.historyTurns,
|
|
225
|
+
ctx,
|
|
226
|
+
);
|
|
227
|
+
const continueTurn = {
|
|
228
|
+
...turn,
|
|
229
|
+
queueLane: "priority" as const,
|
|
230
|
+
laneOrder: Number.MIN_SAFE_INTEGER + turn.queueOrder,
|
|
231
|
+
statusSummary: "continue",
|
|
232
|
+
};
|
|
233
|
+
deps.telegramQueueStore.setQueuedItems(enqueuePlan.remainingItems);
|
|
234
|
+
deps.queueMutationRuntime.append(continueTurn, ctx);
|
|
235
|
+
deps.dispatchNextQueuedTelegramTurn(ctx);
|
|
236
|
+
};
|
|
237
|
+
const reservedCommandNames = new Set(
|
|
238
|
+
Commands.TELEGRAM_RESERVED_COMMAND_NAMES,
|
|
239
|
+
);
|
|
240
|
+
const getPromptTemplateCommands = () =>
|
|
241
|
+
PromptTemplates.getTelegramPromptTemplateCommands(
|
|
242
|
+
deps.getCommands(),
|
|
243
|
+
reservedCommandNames,
|
|
244
|
+
);
|
|
164
245
|
const commandHandler = Commands.createTelegramCommandHandlerTargetRuntime<
|
|
165
246
|
TMessage,
|
|
166
247
|
TContext
|
|
@@ -181,15 +262,25 @@ export function createTelegramInboundRouteRuntime<
|
|
|
181
262
|
deps.bridgeRuntime.lifecycle.setCompactionInProgress,
|
|
182
263
|
updateStatus: deps.updateStatus,
|
|
183
264
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
265
|
+
enqueueContinueTurn,
|
|
184
266
|
compact: deps.compact,
|
|
185
267
|
allocateItemOrder: deps.bridgeRuntime.queue.allocateItemOrder,
|
|
186
268
|
allocateControlOrder: deps.bridgeRuntime.queue.allocateControlOrder,
|
|
187
269
|
appendControlItem: deps.queueMutationRuntime.append,
|
|
188
270
|
showStatus: deps.menuActions.sendStatusMessage,
|
|
189
271
|
openModelMenu: deps.menuActions.openModelMenu,
|
|
272
|
+
openThinkingMenu: (message, ctx) => {
|
|
273
|
+
const chatId = (message as { chat: { id: number } }).chat.id;
|
|
274
|
+
return deps.menuActions.openThinkingMenu(chatId, message.message_id, ctx);
|
|
275
|
+
},
|
|
276
|
+
openQueueMenu: (message, ctx) => {
|
|
277
|
+
const chatId = (message as { chat: { id: number } }).chat.id;
|
|
278
|
+
return deps.openQueueMenu(chatId, message.message_id, ctx);
|
|
279
|
+
},
|
|
190
280
|
getAllowedUserId: deps.configStore.getAllowedUserId,
|
|
191
281
|
setAllowedUserId: deps.configStore.setAllowedUserId,
|
|
192
282
|
setMyCommands: deps.setMyCommands,
|
|
283
|
+
getPromptTemplateCommands,
|
|
193
284
|
persistConfig: deps.configStore.persist,
|
|
194
285
|
sendTextReply: deps.sendTextReply,
|
|
195
286
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
@@ -203,14 +294,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
203
294
|
deps.bridgeRuntime.lifecycle.shouldPreserveQueuedTurnsAsHistory,
|
|
204
295
|
setPreserveQueuedTurnsAsHistory:
|
|
205
296
|
deps.bridgeRuntime.lifecycle.setPreserveQueuedTurnsAsHistory,
|
|
206
|
-
createTurn:
|
|
207
|
-
TMessage,
|
|
208
|
-
TContext
|
|
209
|
-
>({
|
|
210
|
-
allocateQueueOrder: deps.bridgeRuntime.queue.allocateItemOrder,
|
|
211
|
-
downloadFile: deps.downloadFile,
|
|
212
|
-
processAttachments: deps.attachmentHandlerRuntime.process,
|
|
213
|
-
}),
|
|
297
|
+
createTurn: promptTurnBuilder,
|
|
214
298
|
updateStatus: deps.updateStatus,
|
|
215
299
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
216
300
|
}).enqueue;
|
|
@@ -220,6 +304,14 @@ export function createTelegramInboundRouteRuntime<
|
|
|
220
304
|
>({
|
|
221
305
|
extractRawText: Media.extractFirstTelegramMessageText,
|
|
222
306
|
handleCommand: commandHandler,
|
|
307
|
+
expandPromptTemplateCommand: (commandName, args) =>
|
|
308
|
+
PromptTemplates.expandTelegramPromptTemplateCommand(
|
|
309
|
+
commandName,
|
|
310
|
+
args,
|
|
311
|
+
getPromptTemplateCommands(),
|
|
312
|
+
),
|
|
313
|
+
replaceMessageText: (message, text) =>
|
|
314
|
+
({ ...message, text, caption: undefined }) as TMessage,
|
|
223
315
|
enqueueTurn: promptEnqueue,
|
|
224
316
|
});
|
|
225
317
|
const mediaDispatch = Media.createTelegramMediaGroupDispatchRuntime<
|
package/lib/runtime.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram bridge runtime-state helpers
|
|
3
|
+
* Zones: pi agent runtime state, telegram session, shared coordination
|
|
3
4
|
* Owns small session-local runtime primitives that are shared by orchestration but are not specific to queueing, rendering, polling, or Telegram transport
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -350,6 +351,7 @@ export function createTelegramTypingLoopStarter<TContext>(
|
|
|
350
351
|
} catch (error) {
|
|
351
352
|
const message =
|
|
352
353
|
error instanceof Error ? error.message : String(error);
|
|
354
|
+
deps.updateStatus(ctx, message);
|
|
353
355
|
deps.recordRuntimeEvent?.("typing", error, {
|
|
354
356
|
chatId: targetChatId,
|
|
355
357
|
});
|
package/lib/setup.ts
CHANGED
package/lib/status.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram status rendering helpers
|
|
3
|
+
* Zones: telegram ui, pi agent diagnostics, tui
|
|
3
4
|
* Builds usage, cost, and context summaries for the interactive Telegram status view
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -41,6 +42,8 @@ export interface TelegramStatusActiveModel {
|
|
|
41
42
|
export interface TelegramStatusContext {
|
|
42
43
|
sessionManager: { getEntries(): TelegramStatusSessionEntry[] };
|
|
43
44
|
getContextUsage(): TelegramContextUsage | undefined;
|
|
45
|
+
isIdle?: () => boolean;
|
|
46
|
+
hasPendingMessages?: () => boolean;
|
|
44
47
|
modelRegistry: {
|
|
45
48
|
isUsingOAuth(model: TelegramStatusActiveModel): boolean;
|
|
46
49
|
};
|
|
@@ -406,12 +409,17 @@ export function buildTelegramStatusBarText(
|
|
|
406
409
|
return `${label} ${theme.fg("muted", "disconnected")}`;
|
|
407
410
|
if (!state.paired)
|
|
408
411
|
return `${label} ${theme.fg("warning", "awaiting pairing")}`;
|
|
409
|
-
const queued =
|
|
412
|
+
const queued = state.queuedStatus
|
|
413
|
+
? theme.fg("muted", state.queuedStatus)
|
|
414
|
+
: "";
|
|
410
415
|
if (state.compactionInProgress) {
|
|
411
416
|
return `${label} ${theme.fg("accent", "compacting")}${queued}`;
|
|
412
417
|
}
|
|
413
418
|
if (state.processing) {
|
|
414
|
-
|
|
419
|
+
const processingStatus = state.processingStatus ?? "processing";
|
|
420
|
+
const processingToken =
|
|
421
|
+
processingStatus === "active" ? "warning" : "accent";
|
|
422
|
+
return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
|
|
415
423
|
}
|
|
416
424
|
return `${label} ${theme.fg("success", "connected")}`;
|
|
417
425
|
}
|
|
@@ -528,6 +536,13 @@ function buildContextSummary(
|
|
|
528
536
|
return `${percent}/${formatTokens(contextWindow)}`;
|
|
529
537
|
}
|
|
530
538
|
|
|
539
|
+
function buildStatusSummary(ctx: TelegramStatusContext): string {
|
|
540
|
+
if (ctx.hasPendingMessages?.()) return "pending";
|
|
541
|
+
if (ctx.isIdle?.() === false) return "active";
|
|
542
|
+
if (ctx.isIdle?.() === true) return "idle";
|
|
543
|
+
return "unknown";
|
|
544
|
+
}
|
|
545
|
+
|
|
531
546
|
export function buildStatusHtml(
|
|
532
547
|
ctx: TelegramStatusContext,
|
|
533
548
|
activeModel: TelegramStatusActiveModel | undefined,
|
|
@@ -536,7 +551,7 @@ export function buildStatusHtml(
|
|
|
536
551
|
const usesSubscription = activeModel
|
|
537
552
|
? ctx.modelRegistry.isUsingOAuth(activeModel)
|
|
538
553
|
: false;
|
|
539
|
-
const lines: string[] = [];
|
|
554
|
+
const lines: string[] = [buildStatusRow("Status", buildStatusSummary(ctx))];
|
|
540
555
|
const usageSummary = buildUsageSummary(stats);
|
|
541
556
|
const costSummary = buildCostSummary(stats, usesSubscription);
|
|
542
557
|
if (usageSummary) {
|
|
@@ -546,8 +561,5 @@ export function buildStatusHtml(
|
|
|
546
561
|
lines.push(buildStatusRow("Cost", costSummary));
|
|
547
562
|
}
|
|
548
563
|
lines.push(buildStatusRow("Context", buildContextSummary(ctx, activeModel)));
|
|
549
|
-
if (lines.length === 0) {
|
|
550
|
-
lines.push(buildStatusRow("Status", "No usage data yet."));
|
|
551
|
-
}
|
|
552
564
|
return lines.join("\n");
|
|
553
565
|
}
|