@llblab/pi-telegram 0.55.1 → 0.56.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 +5 -4
- package/BACKLOG.md +6 -1
- package/CHANGELOG.md +15 -0
- package/README.md +28 -17
- package/dist/lib/bindings.d.ts +8 -4
- package/dist/lib/bindings.js +41 -1
- package/dist/lib/bus-follower.d.ts +1 -0
- package/dist/lib/bus-follower.js +10 -5
- package/dist/lib/bus-leader.d.ts +2 -0
- package/dist/lib/bus-leader.js +34 -20
- package/dist/lib/bus.d.ts +8 -0
- package/dist/lib/bus.js +21 -7
- package/dist/lib/commands.d.ts +1 -1
- package/dist/lib/commands.js +33 -28
- package/dist/lib/extension.js +18 -3
- package/dist/lib/lifecycle.d.ts +2 -1
- package/dist/lib/lifecycle.js +28 -20
- package/dist/lib/locks.d.ts +6 -2
- package/dist/lib/locks.js +3 -3
- 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.d.ts +6 -2
- package/dist/lib/polling.js +5 -3
- package/dist/lib/queue.d.ts +57 -5
- package/dist/lib/queue.js +313 -25
- package/dist/lib/routing.d.ts +4 -0
- package/dist/lib/routing.js +62 -1
- package/dist/lib/status.d.ts +1 -2
- package/dist/lib/status.js +6 -7
- package/dist/lib/sync.js +11 -1
- package/dist/lib/threads.d.ts +11 -0
- package/dist/lib/threads.js +57 -6
- package/dist/lib/updates.d.ts +12 -7
- package/dist/lib/updates.js +44 -9
- package/dist/package.json +1 -1
- package/docs/README.md +1 -1
- package/docs/architecture.md +24 -13
- package/docs/callback-namespaces.md +4 -0
- package/docs/multi-instance-bus.md +6 -4
- package/docs/ui-style.md +14 -10
- package/lib/bindings.ts +52 -3
- package/lib/bus-follower.ts +13 -5
- package/lib/bus-leader.ts +37 -23
- package/lib/bus.ts +23 -7
- package/lib/commands.ts +52 -47
- package/lib/extension.ts +18 -2
- package/lib/lifecycle.ts +34 -28
- package/lib/locks.ts +5 -2
- package/lib/menu-queue.ts +116 -162
- package/lib/pi.ts +8 -0
- package/lib/polling.ts +7 -2
- package/lib/queue.ts +328 -33
- package/lib/routing.ts +51 -1
- package/lib/status.ts +7 -7
- package/lib/sync.ts +11 -0
- package/lib/threads.ts +57 -5
- package/lib/updates.ts +44 -15
- package/package.json +1 -1
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(
|
package/lib/pi.ts
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
type SessionShutdownEvent,
|
|
21
21
|
type SessionStartEvent,
|
|
22
22
|
type SlashCommandInfo,
|
|
23
|
+
type TurnEndEvent,
|
|
24
|
+
type TurnEndEventResult,
|
|
23
25
|
type UIPromptEndEvent,
|
|
24
26
|
type UIPromptStartEvent,
|
|
25
27
|
SettingsManager,
|
|
@@ -41,6 +43,8 @@ export type {
|
|
|
41
43
|
SessionShutdownEvent,
|
|
42
44
|
SessionStartEvent,
|
|
43
45
|
SlashCommandInfo,
|
|
46
|
+
TurnEndEvent,
|
|
47
|
+
TurnEndEventResult,
|
|
44
48
|
UIPromptEndEvent,
|
|
45
49
|
UIPromptStartEvent,
|
|
46
50
|
};
|
|
@@ -294,6 +298,10 @@ export function isExtensionContextIdle(ctx: ExtensionContext): boolean {
|
|
|
294
298
|
return ctx.isIdle();
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
export function isExtensionContextAbortRequested(ctx: ExtensionContext): boolean {
|
|
302
|
+
return ctx.signal?.aborted === true;
|
|
303
|
+
}
|
|
304
|
+
|
|
297
305
|
export function hasExtensionContextPendingMessages(
|
|
298
306
|
ctx: ExtensionContext,
|
|
299
307
|
): boolean {
|
package/lib/polling.ts
CHANGED
|
@@ -757,6 +757,7 @@ export interface TelegramThreadAwarePollingPorts<TContext, TOwner> {
|
|
|
757
757
|
registerFollowerWithOwner: (
|
|
758
758
|
ctx: TContext,
|
|
759
759
|
owner: TOwner,
|
|
760
|
+
options?: { recoverConnection?: boolean },
|
|
760
761
|
) => Promise<boolean | undefined>;
|
|
761
762
|
restoreFollowerWithOwner: (
|
|
762
763
|
ctx: TContext,
|
|
@@ -776,6 +777,7 @@ export interface TelegramThreadAwarePollingControls<TContext, TOwner> {
|
|
|
776
777
|
registerFollowerWithLeader: (
|
|
777
778
|
ctx: TContext,
|
|
778
779
|
owner: TOwner,
|
|
780
|
+
options?: { recoverConnection?: boolean },
|
|
779
781
|
) => Promise<boolean | undefined>;
|
|
780
782
|
restoreFollowerWithLeader?: (
|
|
781
783
|
ctx: TContext,
|
|
@@ -1180,9 +1182,10 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1180
1182
|
const registerFollowerWithOwner = async (
|
|
1181
1183
|
ctx: TContext,
|
|
1182
1184
|
owner: TOwner,
|
|
1185
|
+
options?: { recoverConnection?: boolean },
|
|
1183
1186
|
): Promise<boolean | undefined> => {
|
|
1184
1187
|
if (!(await refreshFollowerState())) return undefined;
|
|
1185
|
-
return deps.registerFollowerWithLeader(ctx, owner);
|
|
1188
|
+
return deps.registerFollowerWithLeader(ctx, owner, options);
|
|
1186
1189
|
};
|
|
1187
1190
|
const restoreFollowerWithOwner = async (
|
|
1188
1191
|
ctx: TContext,
|
|
@@ -1303,7 +1306,9 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
|
|
|
1303
1306
|
if (threadModeEnabled) consecutiveDisabledProbes = 0;
|
|
1304
1307
|
const botState = deps.topicTargetStore.getBotState();
|
|
1305
1308
|
const current = botState.threadMode;
|
|
1306
|
-
|
|
1309
|
+
// Persisted capability is not proof that this owner started its local bus.
|
|
1310
|
+
if (threadModeEnabled && current === "enabled" &&
|
|
1311
|
+
(deps.getPollingStartedWithTelegramBus() || !deps.ownsLock(ctx))) return;
|
|
1307
1312
|
if (!threadModeEnabled && current === "disabled") {
|
|
1308
1313
|
if (
|
|
1309
1314
|
!deps.ownsLock(ctx) ||
|