@llblab/pi-telegram 0.55.1 → 0.56.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 +3 -2
- package/BACKLOG.md +4 -1
- package/CHANGELOG.md +9 -0
- package/README.md +26 -17
- package/dist/lib/bindings.d.ts +8 -4
- package/dist/lib/bindings.js +41 -1
- package/dist/lib/bus-follower.js +5 -1
- package/dist/lib/bus-leader.js +9 -1
- package/dist/lib/bus.d.ts +3 -0
- package/dist/lib/bus.js +6 -0
- package/dist/lib/extension.js +14 -2
- package/dist/lib/lifecycle.d.ts +2 -1
- package/dist/lib/lifecycle.js +28 -20
- package/dist/lib/menu-queue.d.ts +1 -0
- package/dist/lib/menu-queue.js +102 -99
- package/dist/lib/pi.d.ts +3 -2
- package/dist/lib/pi.js +3 -0
- package/dist/lib/polling.js +3 -1
- package/dist/lib/queue.d.ts +57 -5
- package/dist/lib/queue.js +313 -25
- package/dist/lib/status.d.ts +1 -2
- package/dist/lib/status.js +6 -7
- package/dist/lib/updates.d.ts +8 -6
- package/dist/lib/updates.js +20 -4
- package/dist/package.json +1 -1
- package/docs/README.md +1 -1
- package/docs/architecture.md +14 -13
- package/docs/callback-namespaces.md +4 -0
- package/docs/multi-instance-bus.md +2 -2
- package/docs/ui-style.md +14 -10
- package/lib/bindings.ts +52 -3
- package/lib/bus-follower.ts +5 -0
- package/lib/bus-leader.ts +9 -0
- package/lib/bus.ts +7 -0
- package/lib/extension.ts +14 -1
- package/lib/lifecycle.ts +34 -28
- package/lib/menu-queue.ts +116 -162
- package/lib/pi.ts +8 -0
- package/lib/polling.ts +3 -1
- package/lib/queue.ts +328 -33
- package/lib/status.ts +7 -7
- package/lib/updates.ts +22 -10
- package/package.json +1 -1
package/lib/lifecycle.ts
CHANGED
|
@@ -26,6 +26,8 @@ import type {
|
|
|
26
26
|
ToolExecutionEndEvent,
|
|
27
27
|
ToolExecutionStartEvent,
|
|
28
28
|
ToolExecutionUpdateEvent,
|
|
29
|
+
TurnEndEvent,
|
|
30
|
+
TurnEndEventResult,
|
|
29
31
|
UIPromptEndEvent,
|
|
30
32
|
UIPromptStartEvent,
|
|
31
33
|
} from "./pi.ts";
|
|
@@ -100,6 +102,7 @@ export interface TelegramLifecycleRegistrationDeps {
|
|
|
100
102
|
event: { model: TelegramLifecycleModel },
|
|
101
103
|
ctx: ExtensionContext,
|
|
102
104
|
) => Promise<void> | void;
|
|
105
|
+
onTurnEnd?: (event: TurnEndEvent, ctx: ExtensionContext) => TurnEndEventResult | undefined;
|
|
103
106
|
onAgentStart: (
|
|
104
107
|
event: AgentStartEvent,
|
|
105
108
|
ctx: ExtensionContext,
|
|
@@ -575,7 +578,6 @@ export function createTelegramBridgeSessionLifecycleAssembly<
|
|
|
575
578
|
>(
|
|
576
579
|
deps: TelegramBridgeSessionLifecycleAssemblyDeps<TQueueItem, TModel>,
|
|
577
580
|
): TelegramSessionLifecycleHooks {
|
|
578
|
-
const isSessionActive = deps.contextStore.isCurrent;
|
|
579
581
|
const suspendForReplacement =
|
|
580
582
|
BusFollower.createTelegramBusFollowerSessionReplacementSuspender({
|
|
581
583
|
registrationState: deps.follower.registrationState,
|
|
@@ -583,32 +585,44 @@ export function createTelegramBridgeSessionLifecycleAssembly<
|
|
|
583
585
|
suspendPolling: deps.follower.suspendPolling,
|
|
584
586
|
recordRuntimeEvent: deps.follower.recordRuntimeEvent,
|
|
585
587
|
});
|
|
586
|
-
|
|
587
|
-
const queueLifecycle =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
588
|
+
// A reused context can be current for a successor; inner awaits need this invocation's generation too.
|
|
589
|
+
const queueLifecycle = (ctx: ExtensionContext, generation: number, preserveTarget = true) =>
|
|
590
|
+
Queue.createTelegramSessionLifecycleRuntime({
|
|
591
|
+
...deps.queue,
|
|
592
|
+
isSessionActive: () => deps.contextStore.isCurrent(ctx, generation),
|
|
593
|
+
stopPolling: () => suspendForReplacement(preserveTarget),
|
|
594
|
+
clearPendingMediaGroups: deps.services.suspendGroupedInput,
|
|
595
|
+
});
|
|
593
596
|
const servicesLifecycle: TelegramSessionLifecycleHooks = {
|
|
594
597
|
async onSessionStart(event, ctx) {
|
|
598
|
+
const generation = deps.contextStore.getGeneration();
|
|
599
|
+
const isCurrent = () => deps.contextStore.isCurrent(ctx, generation);
|
|
595
600
|
const resumeConnection = deps.services.connection?.prepare(event, ctx);
|
|
596
|
-
await queueLifecycle.onSessionStart(event, ctx);
|
|
597
|
-
if (!
|
|
601
|
+
await queueLifecycle(ctx, generation).onSessionStart(event, ctx);
|
|
602
|
+
if (!isCurrent()) return;
|
|
598
603
|
deps.services.resumeGroupedInput(ctx);
|
|
599
604
|
await deps.services.delivery.onSessionStart();
|
|
600
|
-
if (!
|
|
605
|
+
if (!isCurrent()) return;
|
|
601
606
|
if (resumeConnection) resumeConnection();
|
|
602
607
|
else await deps.services.polling.onSessionStart(event, ctx);
|
|
608
|
+
if (!isCurrent()) return;
|
|
603
609
|
deps.services.capabilityMonitor.start(ctx);
|
|
604
610
|
deps.services.queueWatchdog.start(ctx);
|
|
611
|
+
await BusFollower.createTelegramBusFollowerSessionRefreshHook({
|
|
612
|
+
registrationState: deps.follower.registrationState,
|
|
613
|
+
registrationRuntime: deps.follower.registrationRuntime,
|
|
614
|
+
getLeaderState: deps.follower.getLeaderState,
|
|
615
|
+
isSessionActive: isCurrent,
|
|
616
|
+
updateStatus: deps.follower.updateStatus,
|
|
617
|
+
recordRuntimeEvent: deps.follower.recordRuntimeEvent,
|
|
618
|
+
})(event, ctx);
|
|
605
619
|
},
|
|
606
620
|
async onSessionShutdown(event, ctx) {
|
|
607
621
|
const generation = deps.contextStore.getGeneration();
|
|
608
622
|
const isCurrent = () => deps.contextStore.isCurrent(ctx, generation);
|
|
609
623
|
if (!isCurrent()) return;
|
|
610
624
|
deps.services.connection?.onSessionShutdown(event, ctx);
|
|
611
|
-
preserveTarget = event.reason !== "resume";
|
|
625
|
+
const preserveTarget = event.reason !== "resume";
|
|
612
626
|
let preserveThread: (() => Promise<void>) | undefined;
|
|
613
627
|
if (event.reason === "quit") {
|
|
614
628
|
try {
|
|
@@ -625,7 +639,7 @@ export function createTelegramBridgeSessionLifecycleAssembly<
|
|
|
625
639
|
if (!isCurrent()) return;
|
|
626
640
|
deps.services.queueWatchdog.stop();
|
|
627
641
|
deps.services.capabilityMonitor.stop();
|
|
628
|
-
await queueLifecycle.onSessionShutdown(event, ctx);
|
|
642
|
+
await queueLifecycle(ctx, generation, preserveTarget).onSessionShutdown(event, ctx);
|
|
629
643
|
if (!isCurrent()) return;
|
|
630
644
|
await deps.services.inboundWorker.onSessionShutdown();
|
|
631
645
|
if (!isCurrent()) return;
|
|
@@ -638,23 +652,9 @@ export function createTelegramBridgeSessionLifecycleAssembly<
|
|
|
638
652
|
}
|
|
639
653
|
},
|
|
640
654
|
};
|
|
641
|
-
const followerLifecycle = appendTelegramLifecycleHooks(
|
|
642
|
-
servicesLifecycle,
|
|
643
|
-
{
|
|
644
|
-
onSessionStart: BusFollower.createTelegramBusFollowerSessionRefreshHook({
|
|
645
|
-
registrationState: deps.follower.registrationState,
|
|
646
|
-
registrationRuntime: deps.follower.registrationRuntime,
|
|
647
|
-
getLeaderState: deps.follower.getLeaderState,
|
|
648
|
-
isSessionActive,
|
|
649
|
-
updateStatus: deps.follower.updateStatus,
|
|
650
|
-
recordRuntimeEvent: deps.follower.recordRuntimeEvent,
|
|
651
|
-
}),
|
|
652
|
-
},
|
|
653
|
-
isSessionActive,
|
|
654
|
-
);
|
|
655
655
|
return createTelegramSessionGenerationFence(
|
|
656
656
|
deps.contextStore,
|
|
657
|
-
|
|
657
|
+
servicesLifecycle,
|
|
658
658
|
);
|
|
659
659
|
}
|
|
660
660
|
|
|
@@ -1042,6 +1042,12 @@ export function registerTelegramLifecycleHooks(
|
|
|
1042
1042
|
if (!isActive(ctx)) return;
|
|
1043
1043
|
await deps.onAgentStart(event, ctx);
|
|
1044
1044
|
});
|
|
1045
|
+
if (deps.onTurnEnd) {
|
|
1046
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1047
|
+
if (!isActive(ctx)) return;
|
|
1048
|
+
return deps.onTurnEnd?.(event, ctx);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1045
1051
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
1046
1052
|
if (!isActive(ctx)) return;
|
|
1047
1053
|
await deps.onToolExecutionStart(event, ctx);
|
package/lib/menu-queue.ts
CHANGED
|
@@ -37,7 +37,12 @@ interface TelegramQueueMenuItem {
|
|
|
37
37
|
queueOrder: number;
|
|
38
38
|
isGuest: boolean;
|
|
39
39
|
queuePosition: number;
|
|
40
|
-
|
|
40
|
+
queueLane: Queue.TelegramQueueLane;
|
|
41
|
+
canSelectLane: boolean;
|
|
42
|
+
canSelectDisposition: boolean;
|
|
43
|
+
dispatchProtected: boolean;
|
|
44
|
+
attemptFrozen: boolean;
|
|
45
|
+
exceedsIdleCapacity: boolean;
|
|
41
46
|
priorityEmoji?: string;
|
|
42
47
|
reactionSuppressionEmoji?: string;
|
|
43
48
|
hasAttachments: boolean;
|
|
@@ -60,15 +65,26 @@ function getTelegramQueueItemPromptText<Context>(
|
|
|
60
65
|
|
|
61
66
|
function toTelegramQueueMenuItems<Context>(
|
|
62
67
|
items: readonly Queue.TelegramQueueItem<Context>[],
|
|
68
|
+
pendingDispatch = false,
|
|
63
69
|
): TelegramQueueMenuItem[] {
|
|
70
|
+
const head = items[0];
|
|
64
71
|
return items.map((item, index) => {
|
|
72
|
+
const dispatchProtected = pendingDispatch && (item === head ||
|
|
73
|
+
(head?.kind === "prompt" && head.steerPacketTail?.includes(item as Queue.PendingTelegramTurn) === true));
|
|
74
|
+
const attemptFrozen = item.kind === "prompt" && item.steerAttempted === true && !dispatchProtected;
|
|
75
|
+
const editable = item.kind === "prompt" && item.guestQueryId === undefined && item.guestInlineMessageId === undefined && !dispatchProtected && !attemptFrozen;
|
|
65
76
|
return {
|
|
66
77
|
chatId: item.chatId,
|
|
67
78
|
replyToMessageId: item.replyToMessageId,
|
|
68
79
|
queueOrder: item.queueOrder,
|
|
69
80
|
isGuest: item.kind === "prompt" && item.guestQueryId !== undefined,
|
|
70
81
|
queuePosition: index + 1,
|
|
71
|
-
|
|
82
|
+
queueLane: item.queueLane,
|
|
83
|
+
canSelectLane: editable && item.queueLane !== "control",
|
|
84
|
+
canSelectDisposition: editable && (item.queueLane !== "control" || item.sourceMessageIds.includes(item.replyToMessageId)),
|
|
85
|
+
dispatchProtected, attemptFrozen,
|
|
86
|
+
exceedsIdleCapacity: item.kind === "prompt" && item.queueLane === "steer" && !item.steerAttempted &&
|
|
87
|
+
!Queue.isTelegramSteerItemWithinIdleCapacity(item),
|
|
72
88
|
priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
|
|
73
89
|
reactionSuppressionEmoji:
|
|
74
90
|
item.kind === "prompt" ? item.reactionSuppressionEmoji : undefined,
|
|
@@ -98,18 +114,16 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
98
114
|
const refreshRow = [{ text: "🔄 Refresh", callback_data: refreshData }];
|
|
99
115
|
if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
|
|
100
116
|
const rows = items.map((item) => {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
: item.
|
|
104
|
-
? `${item.priorityEmoji ?? "⚡"} `
|
|
105
|
-
: item.hasAttachments
|
|
106
|
-
? "📎 "
|
|
107
|
-
: "";
|
|
117
|
+
const badge = getTelegramQueueMenuLaneBadge(item);
|
|
118
|
+
const prefix = item.reactionSuppressionEmoji ? `${item.reactionSuppressionEmoji} ` :
|
|
119
|
+
badge ? `${badge} ` : item.hasAttachments ? "📎 " : "";
|
|
108
120
|
const position = item.reactionSuppressionEmoji
|
|
109
121
|
? formatSkippedTelegramQueuePosition(item.queuePosition)
|
|
110
122
|
: String(item.queuePosition);
|
|
111
123
|
const ordinalSeparator = item.reactionSuppressionEmoji ? "\u200A" : "";
|
|
112
|
-
const
|
|
124
|
+
const state = item.attemptFrozen ? " · frozen" : item.dispatchProtected ? " · dispatch protected" :
|
|
125
|
+
item.exceedsIdleCapacity ? " · idle capacity" : "";
|
|
126
|
+
const label = `${position}${ordinalSeparator}. ${prefix}${item.statusSummary}${state}`;
|
|
113
127
|
return [
|
|
114
128
|
{
|
|
115
129
|
text: label,
|
|
@@ -165,18 +179,27 @@ function escapeTelegramQueueMenuHtmlPreview(text: string): string {
|
|
|
165
179
|
return truncated ? escaped + suffix : escaped;
|
|
166
180
|
}
|
|
167
181
|
|
|
182
|
+
function getTelegramQueueMenuLaneBadge(item: Pick<TelegramQueueMenuItem, "queueLane" | "priorityEmoji">): string {
|
|
183
|
+
if (item.queueLane === "control") return "⚡";
|
|
184
|
+
if (item.queueLane === "steer") return "🔥";
|
|
185
|
+
if (item.queueLane !== "priority") return "";
|
|
186
|
+
const emoji = item.priorityEmoji?.replace(/\uFE0F/g, "");
|
|
187
|
+
return emoji === "❤" || emoji === "🕊" ? emoji : "👍";
|
|
188
|
+
}
|
|
189
|
+
|
|
168
190
|
function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
: item.isPriority
|
|
172
|
-
? ` ${item.priorityEmoji ?? "⚡"}`
|
|
173
|
-
: "";
|
|
191
|
+
const marker = item.reactionSuppressionEmoji ?? getTelegramQueueMenuLaneBadge(item);
|
|
192
|
+
const badge = marker ? ` ${marker}` : "";
|
|
174
193
|
const position = item.reactionSuppressionEmoji
|
|
175
194
|
? `<s>${item.queuePosition}</s>.`
|
|
176
195
|
: `<b>${item.queuePosition}.</b>`;
|
|
177
196
|
const heading = `${position}${badge}`;
|
|
178
197
|
const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
|
179
|
-
|
|
198
|
+
const note = item.attemptFrozen ? "\n<b>⚠️ Attempt frozen.</b>\nSDK/model use is unconfirmed; not editable or automatically retried." :
|
|
199
|
+
item.dispatchProtected ? "\n<b>⏳ Dispatch protected.</b>\nWaiting for agent_start; SDK/model use is unconfirmed." :
|
|
200
|
+
item.exceedsIdleCapacity ? "\n<b>⚠️ Idle Steer capacity exceeded.</b>\nEdit the source or choose Priority/Normal; source groups are never split." :
|
|
201
|
+
item.queueLane === "control" ? "\nControl lane is producer-owned." : "";
|
|
202
|
+
return `${heading}\n${preview}${note}`;
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
function buildTelegramGuestQueueItemSubmenuReplyMarkup(
|
|
@@ -195,37 +218,18 @@ function buildTelegramGuestQueueItemSubmenuReplyMarkup(
|
|
|
195
218
|
};
|
|
196
219
|
}
|
|
197
220
|
|
|
198
|
-
function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:priority`,
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
text: isPriority ? "⚫️ Normal" : "🔵 Normal",
|
|
214
|
-
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:normal`,
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
[
|
|
218
|
-
{
|
|
219
|
-
text: isSkipped ? "⚫️ Keep" : "🟢 Keep",
|
|
220
|
-
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:keep`,
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
text: isSkipped ? "🔴 Skip" : "⚫️ Skip",
|
|
224
|
-
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:skip`,
|
|
225
|
-
},
|
|
226
|
-
],
|
|
227
|
-
],
|
|
228
|
-
};
|
|
221
|
+
function buildTelegramQueueItemSubmenuReplyMarkup(item: TelegramQueueMenuItem): TelegramQueueMenuReplyMarkup {
|
|
222
|
+
const rows: TelegramQueueMenuReplyMarkup["inline_keyboard"] = [[{ text: "⬆️ Back", callback_data: "queue:list" }]];
|
|
223
|
+
if (item.canSelectLane) rows.push([
|
|
224
|
+
{ text: item.queueLane === "default" ? "🔵 Normal" : "⚫️ Normal", callback_data: `queue:lane-set:${item.chatId}:${item.replyToMessageId}:normal` },
|
|
225
|
+
{ text: item.queueLane === "priority" ? "🟣 Priority" : "⚫️ Priority", callback_data: `queue:lane-set:${item.chatId}:${item.replyToMessageId}:priority` },
|
|
226
|
+
{ text: item.queueLane === "steer" ? "🟡 Steer" : "⚫️ Steer", callback_data: `queue:lane-set:${item.chatId}:${item.replyToMessageId}:steer` },
|
|
227
|
+
]);
|
|
228
|
+
if (item.canSelectDisposition) rows.push([
|
|
229
|
+
{ text: item.reactionSuppressionEmoji ? "⚫️ Keep" : "🟢 Keep", callback_data: `queue:skip-set:${item.chatId}:${item.replyToMessageId}:keep` },
|
|
230
|
+
{ text: item.reactionSuppressionEmoji ? "🔴 Skip" : "⚫️ Skip", callback_data: `queue:skip-set:${item.chatId}:${item.replyToMessageId}:skip` },
|
|
231
|
+
]);
|
|
232
|
+
return { inline_keyboard: rows };
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
|
@@ -236,12 +240,7 @@ interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
|
|
236
240
|
) => TelegramQueueMenuItem | undefined;
|
|
237
241
|
findGuestItem: (queueOrder: number) => TelegramQueueMenuItem | undefined;
|
|
238
242
|
skipGuest: (queueOrder: number, ctx: Context) => Promise<boolean>;
|
|
239
|
-
|
|
240
|
-
setPriority: (
|
|
241
|
-
chatId: number,
|
|
242
|
-
replyToMessageId: number,
|
|
243
|
-
enabled: boolean,
|
|
244
|
-
) => boolean;
|
|
243
|
+
setLane: (chatId: number, replyToMessageId: number, lane: Exclude<Queue.TelegramQueueLane, "control">) => boolean;
|
|
245
244
|
setSkipped: (
|
|
246
245
|
chatId: number,
|
|
247
246
|
replyToMessageId: number,
|
|
@@ -307,7 +306,13 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
307
306
|
}
|
|
308
307
|
const guestSkipMatch = data.match(/^queue:guest-skip:(\d+)$/);
|
|
309
308
|
if (guestSkipMatch) {
|
|
310
|
-
const
|
|
309
|
+
const queueOrder = Number(guestSkipMatch[1]);
|
|
310
|
+
const item = deps.findGuestItem(queueOrder);
|
|
311
|
+
if (item?.dispatchProtected || item?.attemptFrozen) {
|
|
312
|
+
await deps.answerCallbackQuery(callbackQueryId, "Queue item is protected");
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
const skipped = await deps.skipGuest(queueOrder, ctx);
|
|
311
316
|
if (!skipped) {
|
|
312
317
|
await refreshStaleTelegramQueueMenuItem(
|
|
313
318
|
callbackQueryId,
|
|
@@ -339,16 +344,16 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
339
344
|
return true;
|
|
340
345
|
}
|
|
341
346
|
const prioSetMatch = data.match(
|
|
342
|
-
/^queue:prio-set:(\d+):(\d+):(priority|normal)$/,
|
|
347
|
+
/^queue:(?:lane-set|prio-set):(\d+):(\d+):(steer|priority|normal)$/,
|
|
343
348
|
);
|
|
344
349
|
if (prioSetMatch) {
|
|
345
|
-
await
|
|
350
|
+
await handleTelegramQueueMenuLaneSet(
|
|
346
351
|
callbackQueryId,
|
|
347
352
|
replyChatId,
|
|
348
353
|
replyMessageId,
|
|
349
354
|
Number(prioSetMatch[1]),
|
|
350
355
|
Number(prioSetMatch[2]),
|
|
351
|
-
prioSetMatch[3] === "priority",
|
|
356
|
+
prioSetMatch[3] === "normal" ? "default" : prioSetMatch[3] as "steer" | "priority",
|
|
352
357
|
ctx,
|
|
353
358
|
deps,
|
|
354
359
|
);
|
|
@@ -448,7 +453,9 @@ async function handleTelegramGuestQueueMenuPick<Context>(
|
|
|
448
453
|
replyChatId,
|
|
449
454
|
replyMessageId,
|
|
450
455
|
getTelegramQueueMenuItemText(item),
|
|
451
|
-
|
|
456
|
+
item.dispatchProtected || item.attemptFrozen
|
|
457
|
+
? buildTelegramQueueItemSubmenuReplyMarkup(item)
|
|
458
|
+
: buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder),
|
|
452
459
|
);
|
|
453
460
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
454
461
|
}
|
|
@@ -474,12 +481,7 @@ async function handleTelegramQueueMenuPick<Context>(
|
|
|
474
481
|
replyChatId,
|
|
475
482
|
replyMessageId,
|
|
476
483
|
getTelegramQueueMenuItemText(item),
|
|
477
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
478
|
-
chatId,
|
|
479
|
-
msgId,
|
|
480
|
-
item.isPriority,
|
|
481
|
-
item.reactionSuppressionEmoji !== undefined,
|
|
482
|
-
),
|
|
484
|
+
buildTelegramQueueItemSubmenuReplyMarkup(item),
|
|
483
485
|
);
|
|
484
486
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
485
487
|
}
|
|
@@ -502,25 +504,25 @@ async function handleTelegramQueueMenuPriority<Context>(
|
|
|
502
504
|
deps,
|
|
503
505
|
);
|
|
504
506
|
}
|
|
505
|
-
await
|
|
507
|
+
await updateTelegramQueueMenuLane(
|
|
506
508
|
callbackQueryId,
|
|
507
509
|
replyChatId,
|
|
508
510
|
replyMessageId,
|
|
509
511
|
chatId,
|
|
510
512
|
msgId,
|
|
511
|
-
|
|
513
|
+
item.queueLane === "priority" ? "default" : "priority",
|
|
512
514
|
ctx,
|
|
513
515
|
deps,
|
|
514
516
|
);
|
|
515
517
|
}
|
|
516
518
|
|
|
517
|
-
async function
|
|
519
|
+
async function handleTelegramQueueMenuLaneSet<Context>(
|
|
518
520
|
callbackQueryId: string,
|
|
519
521
|
replyChatId: number,
|
|
520
522
|
replyMessageId: number,
|
|
521
523
|
chatId: number,
|
|
522
524
|
msgId: number,
|
|
523
|
-
|
|
525
|
+
lane: Exclude<Queue.TelegramQueueLane, "control">,
|
|
524
526
|
ctx: Context,
|
|
525
527
|
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
526
528
|
): Promise<void> {
|
|
@@ -533,29 +535,32 @@ async function handleTelegramQueueMenuPrioritySet<Context>(
|
|
|
533
535
|
deps,
|
|
534
536
|
);
|
|
535
537
|
}
|
|
536
|
-
await
|
|
538
|
+
await updateTelegramQueueMenuLane(
|
|
537
539
|
callbackQueryId,
|
|
538
540
|
replyChatId,
|
|
539
541
|
replyMessageId,
|
|
540
542
|
chatId,
|
|
541
543
|
msgId,
|
|
542
|
-
|
|
544
|
+
lane,
|
|
543
545
|
ctx,
|
|
544
546
|
deps,
|
|
545
547
|
);
|
|
546
548
|
}
|
|
547
549
|
|
|
548
|
-
async function
|
|
550
|
+
async function updateTelegramQueueMenuLane<Context>(
|
|
549
551
|
callbackQueryId: string,
|
|
550
552
|
replyChatId: number,
|
|
551
553
|
replyMessageId: number,
|
|
552
554
|
chatId: number,
|
|
553
555
|
msgId: number,
|
|
554
|
-
|
|
556
|
+
lane: Exclude<Queue.TelegramQueueLane, "control">,
|
|
555
557
|
ctx: Context,
|
|
556
558
|
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
557
559
|
): Promise<void> {
|
|
558
|
-
deps.
|
|
560
|
+
if (!deps.setLane(chatId, msgId, lane)) {
|
|
561
|
+
await deps.answerCallbackQuery(callbackQueryId, "Queue item is protected");
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
559
564
|
deps.updateStatus(ctx);
|
|
560
565
|
const updated = deps.findItem(chatId, msgId);
|
|
561
566
|
if (!updated) {
|
|
@@ -570,16 +575,11 @@ async function updateTelegramQueueMenuPriority<Context>(
|
|
|
570
575
|
replyChatId,
|
|
571
576
|
replyMessageId,
|
|
572
577
|
getTelegramQueueMenuItemText(updated),
|
|
573
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
574
|
-
chatId,
|
|
575
|
-
msgId,
|
|
576
|
-
updated.isPriority,
|
|
577
|
-
updated.reactionSuppressionEmoji !== undefined,
|
|
578
|
-
),
|
|
578
|
+
buildTelegramQueueItemSubmenuReplyMarkup(updated),
|
|
579
579
|
);
|
|
580
580
|
await deps.answerCallbackQuery(
|
|
581
581
|
callbackQueryId,
|
|
582
|
-
updated.
|
|
582
|
+
updated.queueLane === "steer" ? "Steer selected" : updated.queueLane === "priority" ? "Prioritized" : "Normal priority",
|
|
583
583
|
);
|
|
584
584
|
}
|
|
585
585
|
|
|
@@ -602,7 +602,10 @@ async function handleTelegramQueueMenuSkipSet<Context>(
|
|
|
602
602
|
deps,
|
|
603
603
|
);
|
|
604
604
|
}
|
|
605
|
-
deps.setSkipped(chatId, msgId, skipped)
|
|
605
|
+
if (!deps.setSkipped(chatId, msgId, skipped)) {
|
|
606
|
+
await deps.answerCallbackQuery(callbackQueryId, "Queue item is protected");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
606
609
|
deps.updateStatus(ctx);
|
|
607
610
|
const updated = deps.findItem(chatId, msgId);
|
|
608
611
|
if (!updated) {
|
|
@@ -617,12 +620,7 @@ async function handleTelegramQueueMenuSkipSet<Context>(
|
|
|
617
620
|
replyChatId,
|
|
618
621
|
replyMessageId,
|
|
619
622
|
getTelegramQueueMenuItemText(updated),
|
|
620
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
621
|
-
chatId,
|
|
622
|
-
msgId,
|
|
623
|
-
updated.isPriority,
|
|
624
|
-
updated.reactionSuppressionEmoji !== undefined,
|
|
625
|
-
),
|
|
623
|
+
buildTelegramQueueItemSubmenuReplyMarkup(updated),
|
|
626
624
|
);
|
|
627
625
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
628
626
|
}
|
|
@@ -649,6 +647,7 @@ export function createTelegramQueueMenuRuntime<
|
|
|
649
647
|
Context,
|
|
650
648
|
TModel extends MenuModel = MenuModel,
|
|
651
649
|
>(deps: {
|
|
650
|
+
hasPendingDispatch?: () => boolean;
|
|
652
651
|
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
|
653
652
|
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
654
653
|
sendInteractiveMessage: TelegramMenuMessageRuntimeDeps["sendInteractiveMessage"];
|
|
@@ -695,6 +694,7 @@ export function createTelegramQueueMenuRuntime<
|
|
|
695
694
|
);
|
|
696
695
|
return createOpenQueueMenu<Context, TModel>({
|
|
697
696
|
getQueuedItems: deps.telegramQueueStore.getQueuedItems,
|
|
697
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
698
698
|
getModelMenuState: (chat, context) =>
|
|
699
699
|
deps.getModelMenuState(chat, context, threadId),
|
|
700
700
|
storeModelMenuState: delivery.storeModelMenuState,
|
|
@@ -704,6 +704,7 @@ export function createTelegramQueueMenuRuntime<
|
|
|
704
704
|
})(chatId, replyToMessageId, ctx);
|
|
705
705
|
},
|
|
706
706
|
handleCallbackQuery: createQueueMenuCallbackHandler<Context, TModel>({
|
|
707
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
707
708
|
telegramQueueStore: deps.telegramQueueStore,
|
|
708
709
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
709
710
|
editInteractiveMessage: editQueueMenuMessage,
|
|
@@ -721,6 +722,7 @@ function createOpenQueueMenu<
|
|
|
721
722
|
TModel extends MenuModel = MenuModel,
|
|
722
723
|
>(deps: {
|
|
723
724
|
getQueuedItems: () => Queue.TelegramQueueItem<Context>[];
|
|
725
|
+
hasPendingDispatch?: () => boolean;
|
|
724
726
|
getModelMenuState: (
|
|
725
727
|
chatId: number,
|
|
726
728
|
ctx: Context,
|
|
@@ -739,7 +741,7 @@ function createOpenQueueMenu<
|
|
|
739
741
|
ctx: Context,
|
|
740
742
|
): Promise<void> => {
|
|
741
743
|
const state = await deps.getModelMenuState(chatId, ctx);
|
|
742
|
-
const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems());
|
|
744
|
+
const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems(), deps.hasPendingDispatch?.());
|
|
743
745
|
const text = getTelegramQueueMenuListText(menuItems);
|
|
744
746
|
const messageId = await deps.sendInteractiveMessage(
|
|
745
747
|
chatId,
|
|
@@ -758,6 +760,7 @@ function createQueueMenuCallbackHandler<
|
|
|
758
760
|
Context,
|
|
759
761
|
TModel extends MenuModel = MenuModel,
|
|
760
762
|
>(deps: {
|
|
763
|
+
hasPendingDispatch?: () => boolean;
|
|
761
764
|
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
|
762
765
|
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
763
766
|
editInteractiveMessage: (
|
|
@@ -797,7 +800,7 @@ function createQueueMenuCallbackHandler<
|
|
|
797
800
|
return true;
|
|
798
801
|
}
|
|
799
802
|
const menuItems = toTelegramQueueMenuItems(
|
|
800
|
-
deps.telegramQueueStore.getQueuedItems(),
|
|
803
|
+
deps.telegramQueueStore.getQueuedItems(), deps.hasPendingDispatch?.(),
|
|
801
804
|
);
|
|
802
805
|
await deps.editInteractiveMessage(
|
|
803
806
|
chatId,
|
|
@@ -814,7 +817,7 @@ function createQueueMenuCallbackHandler<
|
|
|
814
817
|
return deps.telegramQueueStore.getQueuedItems();
|
|
815
818
|
};
|
|
816
819
|
const toMenuItems = () => {
|
|
817
|
-
return toTelegramQueueMenuItems(getQueueSnapshot());
|
|
820
|
+
return toTelegramQueueMenuItems(getQueueSnapshot(), deps.hasPendingDispatch?.());
|
|
818
821
|
};
|
|
819
822
|
const findItem = (cId: number, rId: number) => {
|
|
820
823
|
return findTelegramQueueItem(toMenuItems(), cId, rId);
|
|
@@ -827,6 +830,7 @@ function createQueueMenuCallbackHandler<
|
|
|
827
830
|
const flagDeps = {
|
|
828
831
|
getQueueSnapshot,
|
|
829
832
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
833
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
830
834
|
};
|
|
831
835
|
return handleTelegramQueueMenuCallback(
|
|
832
836
|
query.id,
|
|
@@ -845,34 +849,10 @@ function createQueueMenuCallbackHandler<
|
|
|
845
849
|
dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
|
|
846
850
|
});
|
|
847
851
|
},
|
|
848
|
-
|
|
849
|
-
applyQueuedTelegramPromptMenuFlags(
|
|
850
|
-
cId,
|
|
851
|
-
rId,
|
|
852
|
-
ctx,
|
|
853
|
-
flagDeps,
|
|
854
|
-
(item) => ({
|
|
855
|
-
priority: item.queueLane !== "priority",
|
|
856
|
-
skipped: isQueuedTelegramPromptSkipped(item),
|
|
857
|
-
}),
|
|
858
|
-
),
|
|
859
|
-
setPriority: (cId, rId, enabled) =>
|
|
860
|
-
applyQueuedTelegramPromptMenuFlags(
|
|
861
|
-
cId,
|
|
862
|
-
rId,
|
|
863
|
-
ctx,
|
|
864
|
-
flagDeps,
|
|
865
|
-
(item) => ({
|
|
866
|
-
priority: enabled,
|
|
867
|
-
skipped: isQueuedTelegramPromptSkipped(item),
|
|
868
|
-
}),
|
|
869
|
-
),
|
|
852
|
+
setLane: (cId, rId, lane) =>
|
|
853
|
+
applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, () => ({ lane })),
|
|
870
854
|
setSkipped: (cId, rId, skipped) =>
|
|
871
|
-
applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, (
|
|
872
|
-
item.kind === "prompt"
|
|
873
|
-
? { priority: item.queueLane === "priority", skipped }
|
|
874
|
-
: undefined,
|
|
875
|
-
),
|
|
855
|
+
applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, () => ({ skipped })),
|
|
876
856
|
updateQueueMessage: deps.editInteractiveMessage,
|
|
877
857
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
878
858
|
updateStatus: deps.updateStatus,
|
|
@@ -911,43 +891,12 @@ async function skipQueuedTelegramGuestPrompt<Context>(
|
|
|
911
891
|
return true;
|
|
912
892
|
}
|
|
913
893
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
skipped: boolean,
|
|
918
|
-
): Queue.TelegramQueueReactionDisposition {
|
|
919
|
-
if (priority && skipped) {
|
|
920
|
-
return {
|
|
921
|
-
kind: "priority-suppressed",
|
|
922
|
-
priorityEmoji:
|
|
923
|
-
item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡",
|
|
924
|
-
suppressionEmoji:
|
|
925
|
-
item.kind === "prompt" ? (item.reactionSuppressionEmoji ?? "👎") : "👎",
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
if (priority) {
|
|
929
|
-
return {
|
|
930
|
-
kind: "priority",
|
|
931
|
-
emoji: item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡",
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
if (skipped) {
|
|
935
|
-
return {
|
|
936
|
-
kind: "suppressed",
|
|
937
|
-
emoji:
|
|
938
|
-
item.kind === "prompt" ? (item.reactionSuppressionEmoji ?? "👎") : "👎",
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
return { kind: "default" };
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
function isQueuedTelegramPromptSkipped<Context>(
|
|
945
|
-
item: Queue.TelegramQueueItem<Context>,
|
|
946
|
-
): boolean {
|
|
947
|
-
return item.kind === "prompt" && item.reactionSuppressionEmoji !== undefined;
|
|
894
|
+
interface TelegramQueueMenuFlags {
|
|
895
|
+
lane?: Exclude<Queue.TelegramQueueLane, "control">;
|
|
896
|
+
skipped?: boolean;
|
|
948
897
|
}
|
|
949
898
|
|
|
950
|
-
/**
|
|
899
|
+
/** Each menu selection changes one dimension through the existing Queue owner. */
|
|
951
900
|
function applyQueuedTelegramPromptMenuFlags<Context>(
|
|
952
901
|
chatId: number,
|
|
953
902
|
replyToMessageId: number,
|
|
@@ -955,10 +904,9 @@ function applyQueuedTelegramPromptMenuFlags<Context>(
|
|
|
955
904
|
deps: {
|
|
956
905
|
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
|
957
906
|
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
907
|
+
hasPendingDispatch?: () => boolean;
|
|
958
908
|
},
|
|
959
|
-
chooseFlags: (
|
|
960
|
-
item: Queue.TelegramQueueItem<Context>,
|
|
961
|
-
) => { priority: boolean; skipped: boolean } | undefined,
|
|
909
|
+
chooseFlags: (item: Queue.TelegramQueueItem<Context>) => TelegramQueueMenuFlags | undefined,
|
|
962
910
|
): boolean {
|
|
963
911
|
const item = findTelegramQueueItem(
|
|
964
912
|
deps.getQueueSnapshot(),
|
|
@@ -966,13 +914,19 @@ function applyQueuedTelegramPromptMenuFlags<Context>(
|
|
|
966
914
|
replyToMessageId,
|
|
967
915
|
);
|
|
968
916
|
const flags = item ? chooseFlags(item) : undefined;
|
|
969
|
-
if (!item || !flags) return false;
|
|
970
|
-
deps.
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
917
|
+
if (!item || item.kind !== "prompt" || !flags) return false;
|
|
918
|
+
const view = findTelegramQueueItem(toTelegramQueueMenuItems(deps.getQueueSnapshot(), deps.hasPendingDispatch?.()), chatId, replyToMessageId);
|
|
919
|
+
if (!view || (flags.lane !== undefined && !view.canSelectLane) || (flags.skipped !== undefined && !view.canSelectDisposition)) return false;
|
|
920
|
+
const disposition: Extract<Queue.TelegramQueueReactionDisposition, { kind: "reaction-transition" }> = { kind: "reaction-transition" };
|
|
921
|
+
if (flags.lane !== undefined) {
|
|
922
|
+
disposition.promptLane = flags.lane;
|
|
923
|
+
disposition.priorityEmoji = flags.lane === "steer" ? "🔥" : flags.lane === "priority" ?
|
|
924
|
+
getTelegramQueueMenuLaneBadge({ queueLane: "priority", priorityEmoji: item.queueLane === "priority" ? item.priorityEmoji : undefined }) : null;
|
|
925
|
+
}
|
|
926
|
+
if (flags.skipped !== undefined) disposition.suppressionEmoji = flags.skipped ? (item.reactionSuppressionEmoji ?? "👎") : null;
|
|
927
|
+
return deps.queueMutationRuntime.applyReactionByMessageId(replyToMessageId, disposition, ctx) ||
|
|
928
|
+
((flags.lane === undefined || flags.lane === item.queueLane) &&
|
|
929
|
+
(flags.skipped === undefined || flags.skipped === (item.reactionSuppressionEmoji !== undefined)));
|
|
976
930
|
}
|
|
977
931
|
|
|
978
932
|
function createQueueMenuSendMessageAdapter(
|