@llblab/pi-telegram 0.35.2 → 0.36.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 +2 -1
- package/BACKLOG.md +11 -0
- package/CHANGELOG.md +23 -0
- package/README.md +36 -7
- package/docs/README.md +3 -2
- package/docs/architecture.md +30 -3
- package/docs/compact-matrix-literal.md +105 -118
- package/docs/generative-apps.md +310 -0
- package/docs/multi-instance-bus.md +1 -1
- package/docs/outbound.md +2 -2
- package/docs/public-api.md +3 -2
- package/docs/ui-style.md +15 -7
- package/index.ts +14 -0
- package/lib/bindings.ts +118 -8
- package/lib/generative-app-worker.mjs +104 -0
- package/lib/generative-apps.ts +958 -0
- package/lib/menu-queue.ts +105 -112
- package/lib/outbound-buttons.ts +58 -10
- package/lib/outbound-markup.ts +81 -22
- package/lib/outbound.ts +5 -1
- package/lib/prompts.ts +1 -0
- package/lib/queue.ts +98 -42
- package/lib/routing.ts +15 -0
- package/lib/runtime.ts +0 -23
- package/lib/updates.ts +79 -26
- package/package.json +1 -1
- package/skills/generated-control-surface/SKILL.md +12 -4
- package/skills/generative-apps/SKILL.md +114 -0
- package/skills/telegram-bridge/SKILL.md +8 -2
package/lib/menu-queue.ts
CHANGED
|
@@ -88,7 +88,7 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
88
88
|
if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
|
|
89
89
|
const rows = items.map((item, index) => {
|
|
90
90
|
const prefix = item.reactionSuppressionEmoji
|
|
91
|
-
? `${item.reactionSuppressionEmoji}
|
|
91
|
+
? `${item.reactionSuppressionEmoji} `
|
|
92
92
|
: item.isPriority
|
|
93
93
|
? `${item.priorityEmoji ?? "⚡"} `
|
|
94
94
|
: item.hasAttachments
|
|
@@ -163,17 +163,15 @@ function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
|
|
163
163
|
? ` ${item.priorityEmoji ?? "⚡"}`
|
|
164
164
|
: "";
|
|
165
165
|
const heading = `<b>${item.queuePosition}.</b>${badge}`;
|
|
166
|
-
const suppression = item.reactionSuppressionEmoji
|
|
167
|
-
? "\n<i>Suppressed by reaction. Remove it to restore this turn.</i>"
|
|
168
|
-
: "";
|
|
169
166
|
const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
|
170
|
-
return `${heading}
|
|
167
|
+
return `${heading}\n${preview}`;
|
|
171
168
|
}
|
|
172
169
|
|
|
173
170
|
function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
174
171
|
chatId: number,
|
|
175
172
|
replyToMessageId: number,
|
|
176
173
|
isPriority: boolean,
|
|
174
|
+
isSkipped: boolean,
|
|
177
175
|
): TelegramQueueMenuReplyMarkup {
|
|
178
176
|
return {
|
|
179
177
|
inline_keyboard: [
|
|
@@ -190,33 +188,18 @@ function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
|
190
188
|
],
|
|
191
189
|
[
|
|
192
190
|
{
|
|
193
|
-
text: "
|
|
194
|
-
callback_data: `queue:
|
|
191
|
+
text: isSkipped ? "⚫️ Keep" : "🟢 Keep",
|
|
192
|
+
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:keep`,
|
|
195
193
|
},
|
|
196
|
-
],
|
|
197
|
-
],
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function buildTelegramQueueDeleteConfirmationReplyMarkup(
|
|
202
|
-
chatId: number,
|
|
203
|
-
replyToMessageId: number,
|
|
204
|
-
): TelegramQueueMenuReplyMarkup {
|
|
205
|
-
return {
|
|
206
|
-
inline_keyboard: [
|
|
207
|
-
[
|
|
208
194
|
{
|
|
209
|
-
text: "
|
|
210
|
-
callback_data: `queue:
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
text: "❌ No",
|
|
214
|
-
callback_data: `queue:keep:${chatId}:${replyToMessageId}`,
|
|
195
|
+
text: isSkipped ? "🔴 Skip" : "⚫️ Skip",
|
|
196
|
+
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:skip`,
|
|
215
197
|
},
|
|
216
198
|
],
|
|
217
199
|
],
|
|
218
200
|
};
|
|
219
201
|
}
|
|
202
|
+
|
|
220
203
|
interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
|
221
204
|
getQueuedItems: () => TelegramQueueMenuItem[];
|
|
222
205
|
findItem: (
|
|
@@ -229,10 +212,10 @@ interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
|
|
229
212
|
replyToMessageId: number,
|
|
230
213
|
enabled: boolean,
|
|
231
214
|
) => boolean;
|
|
232
|
-
|
|
215
|
+
setSkipped: (
|
|
233
216
|
chatId: number,
|
|
234
217
|
replyToMessageId: number,
|
|
235
|
-
|
|
218
|
+
skipped: boolean,
|
|
236
219
|
) => boolean;
|
|
237
220
|
updateQueueMessage: (
|
|
238
221
|
chatId: number,
|
|
@@ -322,38 +305,17 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
322
305
|
);
|
|
323
306
|
return true;
|
|
324
307
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
replyMessageId,
|
|
331
|
-
Number(deleteMatch[1]),
|
|
332
|
-
Number(deleteMatch[2]),
|
|
333
|
-
deps,
|
|
334
|
-
);
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
const keepMatch = data.match(/^queue:keep:(\d+):(\d+)$/);
|
|
338
|
-
if (keepMatch) {
|
|
339
|
-
await handleTelegramQueueMenuKeep(
|
|
340
|
-
callbackQueryId,
|
|
341
|
-
replyChatId,
|
|
342
|
-
replyMessageId,
|
|
343
|
-
Number(keepMatch[1]),
|
|
344
|
-
Number(keepMatch[2]),
|
|
345
|
-
deps,
|
|
346
|
-
);
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
const confirmDeleteMatch = data.match(/^queue:confirm-delete:(\d+):(\d+)$/);
|
|
350
|
-
if (confirmDeleteMatch) {
|
|
351
|
-
await handleTelegramQueueMenuConfirmDelete(
|
|
308
|
+
const skipSetMatch = data.match(
|
|
309
|
+
/^queue:skip-set:(\d+):(\d+):(skip|keep)$/,
|
|
310
|
+
);
|
|
311
|
+
if (skipSetMatch) {
|
|
312
|
+
await handleTelegramQueueMenuSkipSet(
|
|
352
313
|
callbackQueryId,
|
|
353
314
|
replyChatId,
|
|
354
315
|
replyMessageId,
|
|
355
|
-
Number(
|
|
356
|
-
Number(
|
|
316
|
+
Number(skipSetMatch[1]),
|
|
317
|
+
Number(skipSetMatch[2]),
|
|
318
|
+
skipSetMatch[3] === "skip",
|
|
357
319
|
ctx,
|
|
358
320
|
deps,
|
|
359
321
|
);
|
|
@@ -427,7 +389,12 @@ async function handleTelegramQueueMenuPick<Context>(
|
|
|
427
389
|
replyChatId,
|
|
428
390
|
replyMessageId,
|
|
429
391
|
getTelegramQueueMenuItemText(item),
|
|
430
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
392
|
+
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
393
|
+
chatId,
|
|
394
|
+
msgId,
|
|
395
|
+
item.isPriority,
|
|
396
|
+
item.reactionSuppressionEmoji !== undefined,
|
|
397
|
+
),
|
|
431
398
|
);
|
|
432
399
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
433
400
|
}
|
|
@@ -518,7 +485,12 @@ async function updateTelegramQueueMenuPriority<Context>(
|
|
|
518
485
|
replyChatId,
|
|
519
486
|
replyMessageId,
|
|
520
487
|
getTelegramQueueMenuItemText(updated),
|
|
521
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
488
|
+
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
489
|
+
chatId,
|
|
490
|
+
msgId,
|
|
491
|
+
updated.isPriority,
|
|
492
|
+
updated.reactionSuppressionEmoji !== undefined,
|
|
493
|
+
),
|
|
522
494
|
);
|
|
523
495
|
await deps.answerCallbackQuery(
|
|
524
496
|
callbackQueryId,
|
|
@@ -526,12 +498,14 @@ async function updateTelegramQueueMenuPriority<Context>(
|
|
|
526
498
|
);
|
|
527
499
|
}
|
|
528
500
|
|
|
529
|
-
async function
|
|
501
|
+
async function handleTelegramQueueMenuSkipSet<Context>(
|
|
530
502
|
callbackQueryId: string,
|
|
531
503
|
replyChatId: number,
|
|
532
504
|
replyMessageId: number,
|
|
533
505
|
chatId: number,
|
|
534
506
|
msgId: number,
|
|
507
|
+
skipped: boolean,
|
|
508
|
+
ctx: Context,
|
|
535
509
|
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
536
510
|
): Promise<void> {
|
|
537
511
|
const item = deps.findItem(chatId, msgId);
|
|
@@ -543,25 +517,10 @@ async function handleTelegramQueueMenuDeleteRequest<Context>(
|
|
|
543
517
|
deps,
|
|
544
518
|
);
|
|
545
519
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
buildTelegramQueueDeleteConfirmationReplyMarkup(chatId, msgId),
|
|
551
|
-
);
|
|
552
|
-
await deps.answerCallbackQuery(callbackQueryId);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
async function handleTelegramQueueMenuKeep<Context>(
|
|
556
|
-
callbackQueryId: string,
|
|
557
|
-
replyChatId: number,
|
|
558
|
-
replyMessageId: number,
|
|
559
|
-
chatId: number,
|
|
560
|
-
msgId: number,
|
|
561
|
-
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
562
|
-
): Promise<void> {
|
|
563
|
-
const item = deps.findItem(chatId, msgId);
|
|
564
|
-
if (!item) {
|
|
520
|
+
deps.setSkipped(chatId, msgId, skipped);
|
|
521
|
+
deps.updateStatus(ctx);
|
|
522
|
+
const updated = deps.findItem(chatId, msgId);
|
|
523
|
+
if (!updated) {
|
|
565
524
|
return refreshStaleTelegramQueueMenuItem(
|
|
566
525
|
callbackQueryId,
|
|
567
526
|
replyChatId,
|
|
@@ -572,30 +531,15 @@ async function handleTelegramQueueMenuKeep<Context>(
|
|
|
572
531
|
await deps.updateQueueMessage(
|
|
573
532
|
replyChatId,
|
|
574
533
|
replyMessageId,
|
|
575
|
-
getTelegramQueueMenuItemText(
|
|
576
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
callbackQueryId: string,
|
|
583
|
-
replyChatId: number,
|
|
584
|
-
replyMessageId: number,
|
|
585
|
-
chatId: number,
|
|
586
|
-
msgId: number,
|
|
587
|
-
ctx: Context,
|
|
588
|
-
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
589
|
-
): Promise<void> {
|
|
590
|
-
const removed = deps.cancelItem(chatId, msgId, ctx);
|
|
591
|
-
deps.updateStatus(ctx);
|
|
592
|
-
await updateTelegramQueueMenuList(
|
|
593
|
-
callbackQueryId,
|
|
594
|
-
replyChatId,
|
|
595
|
-
replyMessageId,
|
|
596
|
-
deps,
|
|
597
|
-
removed ? "Deleted from queue." : "Item not found.",
|
|
534
|
+
getTelegramQueueMenuItemText(updated),
|
|
535
|
+
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
536
|
+
chatId,
|
|
537
|
+
msgId,
|
|
538
|
+
updated.isPriority,
|
|
539
|
+
updated.reactionSuppressionEmoji !== undefined,
|
|
540
|
+
),
|
|
598
541
|
);
|
|
542
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
599
543
|
}
|
|
600
544
|
|
|
601
545
|
interface TelegramQueueMenuCallbackQuery {
|
|
@@ -803,8 +747,8 @@ function createQueueMenuCallbackHandler<
|
|
|
803
747
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
804
748
|
});
|
|
805
749
|
},
|
|
806
|
-
|
|
807
|
-
return
|
|
750
|
+
setSkipped: (cId, rId, skipped) => {
|
|
751
|
+
return setQueuedTelegramPromptSkipped(cId, rId, skipped, ctx, {
|
|
808
752
|
getQueueSnapshot,
|
|
809
753
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
810
754
|
});
|
|
@@ -817,6 +761,40 @@ function createQueueMenuCallbackHandler<
|
|
|
817
761
|
};
|
|
818
762
|
}
|
|
819
763
|
|
|
764
|
+
function getQueueMenuReactionDisposition<Context>(
|
|
765
|
+
item: Queue.TelegramQueueItem<Context>,
|
|
766
|
+
priority: boolean,
|
|
767
|
+
skipped: boolean,
|
|
768
|
+
): Queue.TelegramQueueReactionDisposition {
|
|
769
|
+
if (priority && skipped) {
|
|
770
|
+
return {
|
|
771
|
+
kind: "priority-suppressed",
|
|
772
|
+
priorityEmoji:
|
|
773
|
+
item.kind === "prompt" ? item.priorityEmoji ?? "⚡" : "⚡",
|
|
774
|
+
suppressionEmoji:
|
|
775
|
+
item.kind === "prompt"
|
|
776
|
+
? item.reactionSuppressionEmoji ?? "👎"
|
|
777
|
+
: "👎",
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
if (priority) {
|
|
781
|
+
return {
|
|
782
|
+
kind: "priority",
|
|
783
|
+
emoji: item.kind === "prompt" ? item.priorityEmoji ?? "⚡" : "⚡",
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
if (skipped) {
|
|
787
|
+
return {
|
|
788
|
+
kind: "suppressed",
|
|
789
|
+
emoji:
|
|
790
|
+
item.kind === "prompt"
|
|
791
|
+
? item.reactionSuppressionEmoji ?? "👎"
|
|
792
|
+
: "👎",
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
return { kind: "default" };
|
|
796
|
+
}
|
|
797
|
+
|
|
820
798
|
function toggleQueuedTelegramPromptPriority<Context>(
|
|
821
799
|
chatId: number,
|
|
822
800
|
replyToMessageId: number,
|
|
@@ -834,9 +812,12 @@ function toggleQueuedTelegramPromptPriority<Context>(
|
|
|
834
812
|
if (!item) return false;
|
|
835
813
|
deps.queueMutationRuntime.applyReactionByMessageId(
|
|
836
814
|
replyToMessageId,
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
815
|
+
getQueueMenuReactionDisposition(
|
|
816
|
+
item,
|
|
817
|
+
item.queueLane !== "priority",
|
|
818
|
+
item.kind === "prompt" &&
|
|
819
|
+
item.reactionSuppressionEmoji !== undefined,
|
|
820
|
+
),
|
|
840
821
|
ctx,
|
|
841
822
|
);
|
|
842
823
|
return true;
|
|
@@ -860,15 +841,21 @@ function setQueuedTelegramPromptPriority<Context>(
|
|
|
860
841
|
if (!item) return false;
|
|
861
842
|
deps.queueMutationRuntime.applyReactionByMessageId(
|
|
862
843
|
replyToMessageId,
|
|
863
|
-
|
|
844
|
+
getQueueMenuReactionDisposition(
|
|
845
|
+
item,
|
|
846
|
+
enabled,
|
|
847
|
+
item.kind === "prompt" &&
|
|
848
|
+
item.reactionSuppressionEmoji !== undefined,
|
|
849
|
+
),
|
|
864
850
|
ctx,
|
|
865
851
|
);
|
|
866
852
|
return true;
|
|
867
853
|
}
|
|
868
854
|
|
|
869
|
-
function
|
|
855
|
+
function setQueuedTelegramPromptSkipped<Context>(
|
|
870
856
|
chatId: number,
|
|
871
857
|
replyToMessageId: number,
|
|
858
|
+
skipped: boolean,
|
|
872
859
|
ctx: Context,
|
|
873
860
|
deps: {
|
|
874
861
|
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
|
@@ -880,11 +867,17 @@ function cancelQueuedTelegramItem<Context>(
|
|
|
880
867
|
chatId,
|
|
881
868
|
replyToMessageId,
|
|
882
869
|
);
|
|
883
|
-
if (!item) return false;
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
870
|
+
if (!item || item.kind !== "prompt") return false;
|
|
871
|
+
deps.queueMutationRuntime.applyReactionByMessageId(
|
|
872
|
+
replyToMessageId,
|
|
873
|
+
getQueueMenuReactionDisposition(
|
|
874
|
+
item,
|
|
875
|
+
item.queueLane === "priority",
|
|
876
|
+
skipped,
|
|
877
|
+
),
|
|
878
|
+
ctx,
|
|
887
879
|
);
|
|
880
|
+
return true;
|
|
888
881
|
}
|
|
889
882
|
|
|
890
883
|
function createQueueMenuSendMessageAdapter(
|
package/lib/outbound-buttons.ts
CHANGED
|
@@ -24,9 +24,16 @@ import {
|
|
|
24
24
|
const TELEGRAM_BUTTON_CALLBACK_PREFIX = "tgbtn";
|
|
25
25
|
const TELEGRAM_BUTTON_ACTION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
26
|
|
|
27
|
+
export interface TelegramOutboundButtonBinding {
|
|
28
|
+
generation: string;
|
|
29
|
+
app: string;
|
|
30
|
+
revision: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export interface TelegramOutboundButtonAction {
|
|
28
34
|
text: string;
|
|
29
35
|
prompt: string;
|
|
36
|
+
binding?: TelegramOutboundButtonBinding;
|
|
30
37
|
selectedStyle?: TelegramInlineKeyboardButtonStyle;
|
|
31
38
|
}
|
|
32
39
|
|
|
@@ -72,6 +79,11 @@ export interface TelegramButtonCallbackHandlerDeps<TContext = unknown> {
|
|
|
72
79
|
action: TelegramOutboundButtonAction,
|
|
73
80
|
ctx: TContext,
|
|
74
81
|
) => boolean | void;
|
|
82
|
+
invokeBoundAction?: (
|
|
83
|
+
query: TelegramButtonCallbackQuery,
|
|
84
|
+
action: TelegramOutboundButtonAction,
|
|
85
|
+
ctx: TContext,
|
|
86
|
+
) => Promise<false | "new" | "edit">;
|
|
75
87
|
editMessageReplyMarkup?: (
|
|
76
88
|
chatId: number,
|
|
77
89
|
messageId: number,
|
|
@@ -146,6 +158,7 @@ export function createTelegramButtonActionStore(
|
|
|
146
158
|
return {
|
|
147
159
|
text: action.text,
|
|
148
160
|
prompt: action.prompt,
|
|
161
|
+
...(action.binding ? { binding: action.binding } : {}),
|
|
149
162
|
...(action.selectedStyle
|
|
150
163
|
? { selectedStyle: action.selectedStyle }
|
|
151
164
|
: {}),
|
|
@@ -159,7 +172,10 @@ const DEFAULT_TELEGRAM_BUTTON_REPLY_MARKDOWN =
|
|
|
159
172
|
|
|
160
173
|
export function planTelegramButtonReply(
|
|
161
174
|
markdown: string,
|
|
162
|
-
deps: {
|
|
175
|
+
deps: {
|
|
176
|
+
registerAction: (action: TelegramOutboundButtonAction) => string;
|
|
177
|
+
binding?: TelegramOutboundButtonBinding;
|
|
178
|
+
},
|
|
163
179
|
): TelegramButtonReplyPlan {
|
|
164
180
|
const keyboard: TelegramOutboundButtonMarkup["inline_keyboard"] = [];
|
|
165
181
|
const stripped = replaceTopLevelHtmlComments(markdown, (comment) => {
|
|
@@ -167,15 +183,20 @@ export function planTelegramButtonReply(
|
|
|
167
183
|
parseTopLevelTelegramComment(comment, candidate),
|
|
168
184
|
);
|
|
169
185
|
if (!command) return comment.raw;
|
|
170
|
-
const payloadRows = parseTelegramActionPayloadRows(comment, command)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
const payloadRows = parseTelegramActionPayloadRows(comment, command);
|
|
187
|
+
if (!payloadRows) return "";
|
|
188
|
+
const actionRows = payloadRows.map((payloadRow) =>
|
|
189
|
+
payloadRow.map(parseTelegramButtonAction),
|
|
190
|
+
);
|
|
191
|
+
if (actionRows.some((row) => row.some((action) => !action))) return "";
|
|
192
|
+
for (const actionRow of actionRows) {
|
|
193
|
+
keyboard.push(actionRow.map((action) => ({
|
|
194
|
+
text: action!.text,
|
|
195
|
+
callback_data: deps.registerAction({
|
|
196
|
+
...action!,
|
|
197
|
+
...(deps.binding ? { binding: deps.binding } : {}),
|
|
198
|
+
}),
|
|
199
|
+
})));
|
|
179
200
|
}
|
|
180
201
|
return "";
|
|
181
202
|
});
|
|
@@ -262,6 +283,33 @@ export async function handleTelegramButtonCallbackQuery<TContext = unknown>(
|
|
|
262
283
|
return true;
|
|
263
284
|
}
|
|
264
285
|
|
|
286
|
+
if (deps.invokeBoundAction) {
|
|
287
|
+
try {
|
|
288
|
+
const viewMode = await deps.invokeBoundAction(query, action, ctx);
|
|
289
|
+
if (viewMode) {
|
|
290
|
+
if (viewMode === "new" && query.data && query.message?.reply_markup) {
|
|
291
|
+
const selectedMarkup = markTelegramButtonSelected(
|
|
292
|
+
query.message.reply_markup,
|
|
293
|
+
query.data,
|
|
294
|
+
action.selectedStyle,
|
|
295
|
+
);
|
|
296
|
+
if (selectedMarkup && deps.editMessageReplyMarkup) {
|
|
297
|
+
try {
|
|
298
|
+
await deps.editMessageReplyMarkup(chatId, messageId, selectedMarkup);
|
|
299
|
+
} catch {
|
|
300
|
+
// The action already succeeded; old-surface styling is best-effort.
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
await deps.answerCallbackQuery(query.id, "Done.");
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
} catch (error) {
|
|
308
|
+
await deps.answerCallbackQuery(query.id, "Generative App action failed.");
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
265
313
|
const enqueued = deps.enqueueButtonPrompt(query, action, ctx);
|
|
266
314
|
if (enqueued === false) {
|
|
267
315
|
await deps.answerCallbackQuery(query.id, "Already queued.");
|
package/lib/outbound-markup.ts
CHANGED
|
@@ -207,7 +207,7 @@ export function parseTelegramActionPayload(
|
|
|
207
207
|
|
|
208
208
|
const TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
209
209
|
|
|
210
|
-
function
|
|
210
|
+
function parseTelegramAdaptiveActionPayloadRows(
|
|
211
211
|
source: string,
|
|
212
212
|
): Record<string, unknown>[][] | undefined {
|
|
213
213
|
let offset = 0;
|
|
@@ -219,18 +219,23 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
219
219
|
const skipWhitespace = (): void => {
|
|
220
220
|
while (isStructuralWhitespace(source[offset])) offset += 1;
|
|
221
221
|
};
|
|
222
|
+
const consumeOptionalSeparator = (): boolean => {
|
|
223
|
+
skipWhitespace();
|
|
224
|
+
if (source[offset] !== ",") return true;
|
|
225
|
+
offset += 1;
|
|
226
|
+
skipWhitespace();
|
|
227
|
+
return source[offset] !== "," && source[offset] !== "]";
|
|
228
|
+
};
|
|
222
229
|
const normalizeAtom = (value: string): string | undefined => {
|
|
223
230
|
const normalized = value.trim();
|
|
224
231
|
return normalized && !TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN.test(normalized)
|
|
225
232
|
? normalized
|
|
226
233
|
: undefined;
|
|
227
234
|
};
|
|
228
|
-
const
|
|
235
|
+
const parseCompactCell = (): Record<string, unknown> | undefined => {
|
|
229
236
|
if (source[offset] !== "{") return undefined;
|
|
230
237
|
offset += 1;
|
|
231
|
-
const
|
|
232
|
-
const valueSource: string[] = [];
|
|
233
|
-
let hasSeparator = false;
|
|
238
|
+
const atomSources: string[][] = [[]];
|
|
234
239
|
while (offset < source.length) {
|
|
235
240
|
const character = source[offset]!;
|
|
236
241
|
if (character === "\\") {
|
|
@@ -238,31 +243,85 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
238
243
|
if (escaped !== "|" && escaped !== "}" && escaped !== "\\") {
|
|
239
244
|
return undefined;
|
|
240
245
|
}
|
|
241
|
-
|
|
242
|
-
else keySource.push(escaped);
|
|
246
|
+
atomSources.at(-1)!.push(escaped);
|
|
243
247
|
offset += 2;
|
|
244
248
|
continue;
|
|
245
249
|
}
|
|
246
250
|
if (character === "|") {
|
|
247
|
-
if (
|
|
248
|
-
|
|
251
|
+
if (atomSources.length >= 3) return undefined;
|
|
252
|
+
atomSources.push([]);
|
|
249
253
|
offset += 1;
|
|
250
254
|
continue;
|
|
251
255
|
}
|
|
252
256
|
if (character === "}") {
|
|
253
257
|
offset += 1;
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
258
|
+
const atoms = atomSources.map((atom) =>
|
|
259
|
+
normalizeAtom(atom.join("")),
|
|
260
|
+
);
|
|
261
|
+
if (atoms.some((atom) => atom === undefined)) return undefined;
|
|
262
|
+
const [label, prompt, selectedStyle] = atoms as string[];
|
|
263
|
+
if (atoms.length === 1) return { value: label };
|
|
264
|
+
if (atoms.length === 2) return { label, prompt };
|
|
265
|
+
if (
|
|
266
|
+
selectedStyle !== "primary" &&
|
|
267
|
+
selectedStyle !== "success" &&
|
|
268
|
+
selectedStyle !== "danger"
|
|
269
|
+
) return undefined;
|
|
270
|
+
return { label, prompt, selected_style: selectedStyle };
|
|
259
271
|
}
|
|
260
|
-
|
|
261
|
-
else keySource.push(character);
|
|
272
|
+
atomSources.at(-1)!.push(character);
|
|
262
273
|
offset += 1;
|
|
263
274
|
}
|
|
264
275
|
return undefined;
|
|
265
276
|
};
|
|
277
|
+
const parseJsonObjectCell = (): Record<string, unknown> | undefined => {
|
|
278
|
+
if (source[offset] !== "{") return undefined;
|
|
279
|
+
const start = offset;
|
|
280
|
+
const stack: string[] = [];
|
|
281
|
+
let inString = false;
|
|
282
|
+
let escaped = false;
|
|
283
|
+
for (let index = start; index < source.length; index += 1) {
|
|
284
|
+
const character = source[index]!;
|
|
285
|
+
if (inString) {
|
|
286
|
+
if (escaped) escaped = false;
|
|
287
|
+
else if (character === "\\") escaped = true;
|
|
288
|
+
else if (character === '"') inString = false;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (character === '"') {
|
|
292
|
+
inString = true;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (character === "{" || character === "[") {
|
|
296
|
+
stack.push(character);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (character !== "}" && character !== "]") continue;
|
|
300
|
+
const opening = stack.pop();
|
|
301
|
+
if (
|
|
302
|
+
(character === "}" && opening !== "{") ||
|
|
303
|
+
(character === "]" && opening !== "[")
|
|
304
|
+
) return undefined;
|
|
305
|
+
if (stack.length > 0) continue;
|
|
306
|
+
const candidate = source.slice(start, index + 1);
|
|
307
|
+
try {
|
|
308
|
+
const value: unknown = JSON.parse(candidate);
|
|
309
|
+
if (!isTelegramActionPayload(value)) return undefined;
|
|
310
|
+
offset = index + 1;
|
|
311
|
+
return value;
|
|
312
|
+
} catch {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
};
|
|
318
|
+
const parseCell = (): Record<string, unknown> | undefined => {
|
|
319
|
+
const start = offset;
|
|
320
|
+
const jsonCell = parseJsonObjectCell();
|
|
321
|
+
if (jsonCell) return jsonCell;
|
|
322
|
+
offset = start;
|
|
323
|
+
return parseCompactCell();
|
|
324
|
+
};
|
|
266
325
|
const parseRow = (): Record<string, unknown>[] | undefined => {
|
|
267
326
|
if (source[offset] !== "[") return undefined;
|
|
268
327
|
offset += 1;
|
|
@@ -277,6 +336,7 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
277
336
|
const cell = parseCell();
|
|
278
337
|
if (!cell) return undefined;
|
|
279
338
|
row.push(cell);
|
|
339
|
+
if (!consumeOptionalSeparator()) return undefined;
|
|
280
340
|
}
|
|
281
341
|
return undefined;
|
|
282
342
|
};
|
|
@@ -295,15 +355,14 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
295
355
|
const cell = parseCell();
|
|
296
356
|
if (!cell) return undefined;
|
|
297
357
|
rows.push([cell]);
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
if (character === "[") {
|
|
358
|
+
} else if (character === "[") {
|
|
301
359
|
const row = parseRow();
|
|
302
360
|
if (!row) return undefined;
|
|
303
361
|
rows.push(row);
|
|
304
|
-
|
|
362
|
+
} else {
|
|
363
|
+
return undefined;
|
|
305
364
|
}
|
|
306
|
-
return undefined;
|
|
365
|
+
if (!consumeOptionalSeparator()) return undefined;
|
|
307
366
|
}
|
|
308
367
|
return undefined;
|
|
309
368
|
};
|
|
@@ -349,7 +408,7 @@ export function parseTelegramActionPayloadRows(
|
|
|
349
408
|
}
|
|
350
409
|
return rows;
|
|
351
410
|
} catch {
|
|
352
|
-
return
|
|
411
|
+
return parseTelegramAdaptiveActionPayloadRows(payload.source);
|
|
353
412
|
}
|
|
354
413
|
}
|
|
355
414
|
if (payload.hasBody) return undefined;
|
package/lib/outbound.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import {
|
|
22
22
|
planTelegramButtonReply,
|
|
23
23
|
type TelegramButtonActionStore,
|
|
24
|
+
type TelegramOutboundButtonBinding,
|
|
24
25
|
type TelegramOutboundButtonMarkup,
|
|
25
26
|
} from "./outbound-buttons.ts";
|
|
26
27
|
import {
|
|
@@ -824,6 +825,7 @@ export {
|
|
|
824
825
|
type TelegramButtonCallbackQuery,
|
|
825
826
|
type TelegramButtonReplyPlan,
|
|
826
827
|
type TelegramOutboundButtonAction,
|
|
828
|
+
type TelegramOutboundButtonBinding,
|
|
827
829
|
type TelegramOutboundButtonMarkup,
|
|
828
830
|
type TelegramOutboundButtonStoredAction,
|
|
829
831
|
} from "./outbound-buttons.ts";
|
|
@@ -832,10 +834,12 @@ export function createTelegramOutboundReplyPlanner(
|
|
|
832
834
|
store: Pick<TelegramButtonActionStore, "register">,
|
|
833
835
|
): (
|
|
834
836
|
markdown: string,
|
|
837
|
+
options?: { binding?: TelegramOutboundButtonBinding },
|
|
835
838
|
) => TelegramOutboundReplyPlan<TelegramOutboundButtonMarkup> {
|
|
836
|
-
return (markdown) => {
|
|
839
|
+
return (markdown, options) => {
|
|
837
840
|
const buttonReply = planTelegramButtonReply(markdown, {
|
|
838
841
|
registerAction: store.register,
|
|
842
|
+
...(options?.binding ? { binding: options.binding } : {}),
|
|
839
843
|
});
|
|
840
844
|
|
|
841
845
|
// Button replies can also contain <!-- telegram_voice --> markup
|
package/lib/prompts.ts
CHANGED