@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/dist/lib/menu-queue.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Owns queue-menu rendering, queue item callbacks, and queue-menu runtime adapters while core queue mechanics stay in queue
|
|
5
5
|
*/
|
|
6
6
|
import { createTelegramMenuDelivery, } from "./menu-model.js";
|
|
7
|
+
import * as Queue from "./queue.js";
|
|
7
8
|
const QUEUE_ITEM_PROMPT_HTML_LIMIT = 3600;
|
|
8
9
|
const QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX = "\n… [truncated]";
|
|
9
10
|
const EMPTY_QUEUE_REFRESH_TITLES = [
|
|
@@ -29,15 +30,25 @@ function getTelegramQueueItemPromptText(item) {
|
|
|
29
30
|
.join("\n")
|
|
30
31
|
.trim() || item.statusSummary);
|
|
31
32
|
}
|
|
32
|
-
function toTelegramQueueMenuItems(items) {
|
|
33
|
+
function toTelegramQueueMenuItems(items, pendingDispatch = false) {
|
|
34
|
+
const head = items[0];
|
|
33
35
|
return items.map((item, index) => {
|
|
36
|
+
const dispatchProtected = pendingDispatch && (item === head ||
|
|
37
|
+
(head?.kind === "prompt" && head.steerPacketTail?.includes(item) === true));
|
|
38
|
+
const attemptFrozen = item.kind === "prompt" && item.steerAttempted === true && !dispatchProtected;
|
|
39
|
+
const editable = item.kind === "prompt" && item.guestQueryId === undefined && item.guestInlineMessageId === undefined && !dispatchProtected && !attemptFrozen;
|
|
34
40
|
return {
|
|
35
41
|
chatId: item.chatId,
|
|
36
42
|
replyToMessageId: item.replyToMessageId,
|
|
37
43
|
queueOrder: item.queueOrder,
|
|
38
44
|
isGuest: item.kind === "prompt" && item.guestQueryId !== undefined,
|
|
39
45
|
queuePosition: index + 1,
|
|
40
|
-
|
|
46
|
+
queueLane: item.queueLane,
|
|
47
|
+
canSelectLane: editable && item.queueLane !== "control",
|
|
48
|
+
canSelectDisposition: editable && (item.queueLane !== "control" || item.sourceMessageIds.includes(item.replyToMessageId)),
|
|
49
|
+
dispatchProtected, attemptFrozen,
|
|
50
|
+
exceedsIdleCapacity: item.kind === "prompt" && item.queueLane === "steer" && !item.steerAttempted &&
|
|
51
|
+
!Queue.isTelegramSteerItemWithinIdleCapacity(item),
|
|
41
52
|
priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
|
|
42
53
|
reactionSuppressionEmoji: item.kind === "prompt" ? item.reactionSuppressionEmoji : undefined,
|
|
43
54
|
hasAttachments: item.kind === "prompt" && item.queuedAttachments.length > 0,
|
|
@@ -59,18 +70,16 @@ function buildTelegramQueueMenuReplyMarkup(items, emptyRefreshIndex = 0) {
|
|
|
59
70
|
if (items.length === 0)
|
|
60
71
|
return { inline_keyboard: [backRow, refreshRow] };
|
|
61
72
|
const rows = items.map((item) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
: item.
|
|
65
|
-
? `${item.priorityEmoji ?? "⚡"} `
|
|
66
|
-
: item.hasAttachments
|
|
67
|
-
? "📎 "
|
|
68
|
-
: "";
|
|
73
|
+
const badge = getTelegramQueueMenuLaneBadge(item);
|
|
74
|
+
const prefix = item.reactionSuppressionEmoji ? `${item.reactionSuppressionEmoji} ` :
|
|
75
|
+
badge ? `${badge} ` : item.hasAttachments ? "📎 " : "";
|
|
69
76
|
const position = item.reactionSuppressionEmoji
|
|
70
77
|
? formatSkippedTelegramQueuePosition(item.queuePosition)
|
|
71
78
|
: String(item.queuePosition);
|
|
72
79
|
const ordinalSeparator = item.reactionSuppressionEmoji ? "\u200A" : "";
|
|
73
|
-
const
|
|
80
|
+
const state = item.attemptFrozen ? " · frozen" : item.dispatchProtected ? " · dispatch protected" :
|
|
81
|
+
item.exceedsIdleCapacity ? " · idle capacity" : "";
|
|
82
|
+
const label = `${position}${ordinalSeparator}. ${prefix}${item.statusSummary}${state}`;
|
|
74
83
|
return [
|
|
75
84
|
{
|
|
76
85
|
text: label,
|
|
@@ -114,18 +123,29 @@ function escapeTelegramQueueMenuHtmlPreview(text) {
|
|
|
114
123
|
}
|
|
115
124
|
return truncated ? escaped + suffix : escaped;
|
|
116
125
|
}
|
|
126
|
+
function getTelegramQueueMenuLaneBadge(item) {
|
|
127
|
+
if (item.queueLane === "control")
|
|
128
|
+
return "⚡";
|
|
129
|
+
if (item.queueLane === "steer")
|
|
130
|
+
return "🔥";
|
|
131
|
+
if (item.queueLane !== "priority")
|
|
132
|
+
return "";
|
|
133
|
+
const emoji = item.priorityEmoji?.replace(/\uFE0F/g, "");
|
|
134
|
+
return emoji === "❤" || emoji === "🕊" ? emoji : "👍";
|
|
135
|
+
}
|
|
117
136
|
function getTelegramQueueMenuItemText(item) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
: item.isPriority
|
|
121
|
-
? ` ${item.priorityEmoji ?? "⚡"}`
|
|
122
|
-
: "";
|
|
137
|
+
const marker = item.reactionSuppressionEmoji ?? getTelegramQueueMenuLaneBadge(item);
|
|
138
|
+
const badge = marker ? ` ${marker}` : "";
|
|
123
139
|
const position = item.reactionSuppressionEmoji
|
|
124
140
|
? `<s>${item.queuePosition}</s>.`
|
|
125
141
|
: `<b>${item.queuePosition}.</b>`;
|
|
126
142
|
const heading = `${position}${badge}`;
|
|
127
143
|
const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
|
128
|
-
|
|
144
|
+
const note = item.attemptFrozen ? "\n<b>⚠️ Attempt frozen.</b>\nSDK/model use is unconfirmed; not editable or automatically retried." :
|
|
145
|
+
item.dispatchProtected ? "\n<b>⏳ Dispatch protected.</b>\nWaiting for agent_start; SDK/model use is unconfirmed." :
|
|
146
|
+
item.exceedsIdleCapacity ? "\n<b>⚠️ Idle Steer capacity exceeded.</b>\nEdit the source or choose Priority/Normal; source groups are never split." :
|
|
147
|
+
item.queueLane === "control" ? "\nControl lane is producer-owned." : "";
|
|
148
|
+
return `${heading}\n${preview}${note}`;
|
|
129
149
|
}
|
|
130
150
|
function buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder) {
|
|
131
151
|
return {
|
|
@@ -140,32 +160,20 @@ function buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder) {
|
|
|
140
160
|
],
|
|
141
161
|
};
|
|
142
162
|
}
|
|
143
|
-
function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
[
|
|
158
|
-
{
|
|
159
|
-
text: isSkipped ? "⚫️ Keep" : "🟢 Keep",
|
|
160
|
-
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:keep`,
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
text: isSkipped ? "🔴 Skip" : "⚫️ Skip",
|
|
164
|
-
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:skip`,
|
|
165
|
-
},
|
|
166
|
-
],
|
|
167
|
-
],
|
|
168
|
-
};
|
|
163
|
+
function buildTelegramQueueItemSubmenuReplyMarkup(item) {
|
|
164
|
+
const rows = [[{ text: "⬆️ Back", callback_data: "queue:list" }]];
|
|
165
|
+
if (item.canSelectLane)
|
|
166
|
+
rows.push([
|
|
167
|
+
{ text: item.queueLane === "default" ? "🔵 Normal" : "⚫️ Normal", callback_data: `queue:lane-set:${item.chatId}:${item.replyToMessageId}:normal` },
|
|
168
|
+
{ text: item.queueLane === "priority" ? "🟣 Priority" : "⚫️ Priority", callback_data: `queue:lane-set:${item.chatId}:${item.replyToMessageId}:priority` },
|
|
169
|
+
{ text: item.queueLane === "steer" ? "🟡 Steer" : "⚫️ Steer", callback_data: `queue:lane-set:${item.chatId}:${item.replyToMessageId}:steer` },
|
|
170
|
+
]);
|
|
171
|
+
if (item.canSelectDisposition)
|
|
172
|
+
rows.push([
|
|
173
|
+
{ text: item.reactionSuppressionEmoji ? "⚫️ Keep" : "🟢 Keep", callback_data: `queue:skip-set:${item.chatId}:${item.replyToMessageId}:keep` },
|
|
174
|
+
{ text: item.reactionSuppressionEmoji ? "🔴 Skip" : "⚫️ Skip", callback_data: `queue:skip-set:${item.chatId}:${item.replyToMessageId}:skip` },
|
|
175
|
+
]);
|
|
176
|
+
return { inline_keyboard: rows };
|
|
169
177
|
}
|
|
170
178
|
async function handleTelegramQueueMenuCallback(callbackQueryId, data, replyChatId, replyMessageId, ctx, deps) {
|
|
171
179
|
if (!data.startsWith("queue:"))
|
|
@@ -190,7 +198,13 @@ async function handleTelegramQueueMenuCallback(callbackQueryId, data, replyChatI
|
|
|
190
198
|
}
|
|
191
199
|
const guestSkipMatch = data.match(/^queue:guest-skip:(\d+)$/);
|
|
192
200
|
if (guestSkipMatch) {
|
|
193
|
-
const
|
|
201
|
+
const queueOrder = Number(guestSkipMatch[1]);
|
|
202
|
+
const item = deps.findGuestItem(queueOrder);
|
|
203
|
+
if (item?.dispatchProtected || item?.attemptFrozen) {
|
|
204
|
+
await deps.answerCallbackQuery(callbackQueryId, "Queue item is protected");
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
const skipped = await deps.skipGuest(queueOrder, ctx);
|
|
194
208
|
if (!skipped) {
|
|
195
209
|
await refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
196
210
|
return true;
|
|
@@ -203,9 +217,9 @@ async function handleTelegramQueueMenuCallback(callbackQueryId, data, replyChatI
|
|
|
203
217
|
await handleTelegramQueueMenuPick(callbackQueryId, replyChatId, replyMessageId, Number(pickMatch[1]), Number(pickMatch[2]), deps);
|
|
204
218
|
return true;
|
|
205
219
|
}
|
|
206
|
-
const prioSetMatch = data.match(/^queue:prio-set:(\d+):(\d+):(priority|normal)$/);
|
|
220
|
+
const prioSetMatch = data.match(/^queue:(?:lane-set|prio-set):(\d+):(\d+):(steer|priority|normal)$/);
|
|
207
221
|
if (prioSetMatch) {
|
|
208
|
-
await
|
|
222
|
+
await handleTelegramQueueMenuLaneSet(callbackQueryId, replyChatId, replyMessageId, Number(prioSetMatch[1]), Number(prioSetMatch[2]), prioSetMatch[3] === "normal" ? "default" : prioSetMatch[3], ctx, deps);
|
|
209
223
|
return true;
|
|
210
224
|
}
|
|
211
225
|
const prioMatch = data.match(/^queue:prio:(\d+):(\d+)$/);
|
|
@@ -240,7 +254,9 @@ async function handleTelegramGuestQueueMenuPick(callbackQueryId, replyChatId, re
|
|
|
240
254
|
if (!item) {
|
|
241
255
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
242
256
|
}
|
|
243
|
-
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(item),
|
|
257
|
+
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(item), item.dispatchProtected || item.attemptFrozen
|
|
258
|
+
? buildTelegramQueueItemSubmenuReplyMarkup(item)
|
|
259
|
+
: buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder));
|
|
244
260
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
245
261
|
}
|
|
246
262
|
async function handleTelegramQueueMenuPick(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, deps) {
|
|
@@ -248,7 +264,7 @@ async function handleTelegramQueueMenuPick(callbackQueryId, replyChatId, replyMe
|
|
|
248
264
|
if (!item) {
|
|
249
265
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
250
266
|
}
|
|
251
|
-
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(item), buildTelegramQueueItemSubmenuReplyMarkup(
|
|
267
|
+
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(item), buildTelegramQueueItemSubmenuReplyMarkup(item));
|
|
252
268
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
253
269
|
}
|
|
254
270
|
async function handleTelegramQueueMenuPriority(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, ctx, deps) {
|
|
@@ -256,37 +272,43 @@ async function handleTelegramQueueMenuPriority(callbackQueryId, replyChatId, rep
|
|
|
256
272
|
if (!item) {
|
|
257
273
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
258
274
|
}
|
|
259
|
-
await
|
|
275
|
+
await updateTelegramQueueMenuLane(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, item.queueLane === "priority" ? "default" : "priority", ctx, deps);
|
|
260
276
|
}
|
|
261
|
-
async function
|
|
277
|
+
async function handleTelegramQueueMenuLaneSet(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, lane, ctx, deps) {
|
|
262
278
|
const item = deps.findItem(chatId, msgId);
|
|
263
279
|
if (!item) {
|
|
264
280
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
265
281
|
}
|
|
266
|
-
await
|
|
282
|
+
await updateTelegramQueueMenuLane(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, lane, ctx, deps);
|
|
267
283
|
}
|
|
268
|
-
async function
|
|
269
|
-
deps.
|
|
284
|
+
async function updateTelegramQueueMenuLane(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, lane, ctx, deps) {
|
|
285
|
+
if (!deps.setLane(chatId, msgId, lane)) {
|
|
286
|
+
await deps.answerCallbackQuery(callbackQueryId, "Queue item is protected");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
270
289
|
deps.updateStatus(ctx);
|
|
271
290
|
const updated = deps.findItem(chatId, msgId);
|
|
272
291
|
if (!updated) {
|
|
273
292
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
274
293
|
}
|
|
275
|
-
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(updated), buildTelegramQueueItemSubmenuReplyMarkup(
|
|
276
|
-
await deps.answerCallbackQuery(callbackQueryId, updated.
|
|
294
|
+
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(updated), buildTelegramQueueItemSubmenuReplyMarkup(updated));
|
|
295
|
+
await deps.answerCallbackQuery(callbackQueryId, updated.queueLane === "steer" ? "Steer selected" : updated.queueLane === "priority" ? "Prioritized" : "Normal priority");
|
|
277
296
|
}
|
|
278
297
|
async function handleTelegramQueueMenuSkipSet(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, skipped, ctx, deps) {
|
|
279
298
|
const item = deps.findItem(chatId, msgId);
|
|
280
299
|
if (!item) {
|
|
281
300
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
282
301
|
}
|
|
283
|
-
deps.setSkipped(chatId, msgId, skipped)
|
|
302
|
+
if (!deps.setSkipped(chatId, msgId, skipped)) {
|
|
303
|
+
await deps.answerCallbackQuery(callbackQueryId, "Queue item is protected");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
284
306
|
deps.updateStatus(ctx);
|
|
285
307
|
const updated = deps.findItem(chatId, msgId);
|
|
286
308
|
if (!updated) {
|
|
287
309
|
return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
|
|
288
310
|
}
|
|
289
|
-
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(updated), buildTelegramQueueItemSubmenuReplyMarkup(
|
|
311
|
+
await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(updated), buildTelegramQueueItemSubmenuReplyMarkup(updated));
|
|
290
312
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
291
313
|
}
|
|
292
314
|
export function createTelegramQueueMenuRuntime(deps) {
|
|
@@ -297,6 +319,7 @@ export function createTelegramQueueMenuRuntime(deps) {
|
|
|
297
319
|
const delivery = createTelegramMenuDelivery({ chatId, threadId }, options, deps);
|
|
298
320
|
return createOpenQueueMenu({
|
|
299
321
|
getQueuedItems: deps.telegramQueueStore.getQueuedItems,
|
|
322
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
300
323
|
getModelMenuState: (chat, context) => deps.getModelMenuState(chat, context, threadId),
|
|
301
324
|
storeModelMenuState: delivery.storeModelMenuState,
|
|
302
325
|
sendInteractiveMessage: delivery.assertAuthority
|
|
@@ -305,6 +328,7 @@ export function createTelegramQueueMenuRuntime(deps) {
|
|
|
305
328
|
})(chatId, replyToMessageId, ctx);
|
|
306
329
|
},
|
|
307
330
|
handleCallbackQuery: createQueueMenuCallbackHandler({
|
|
331
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
308
332
|
telegramQueueStore: deps.telegramQueueStore,
|
|
309
333
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
310
334
|
editInteractiveMessage: editQueueMenuMessage,
|
|
@@ -319,7 +343,7 @@ export function createTelegramQueueMenuRuntime(deps) {
|
|
|
319
343
|
function createOpenQueueMenu(deps) {
|
|
320
344
|
return async (chatId, replyToMessageId, ctx) => {
|
|
321
345
|
const state = await deps.getModelMenuState(chatId, ctx);
|
|
322
|
-
const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems());
|
|
346
|
+
const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems(), deps.hasPendingDispatch?.());
|
|
323
347
|
const text = getTelegramQueueMenuListText(menuItems);
|
|
324
348
|
const messageId = await deps.sendInteractiveMessage(chatId, replyToMessageId, text, buildTelegramQueueMenuReplyMarkup(menuItems));
|
|
325
349
|
if (messageId === undefined)
|
|
@@ -342,7 +366,7 @@ function createQueueMenuCallbackHandler(deps) {
|
|
|
342
366
|
await deps.answerCallbackQuery(query.id, "Interactive message expired");
|
|
343
367
|
return true;
|
|
344
368
|
}
|
|
345
|
-
const menuItems = toTelegramQueueMenuItems(deps.telegramQueueStore.getQueuedItems());
|
|
369
|
+
const menuItems = toTelegramQueueMenuItems(deps.telegramQueueStore.getQueuedItems(), deps.hasPendingDispatch?.());
|
|
346
370
|
await deps.editInteractiveMessage(chatId, messageId, getTelegramQueueMenuListText(menuItems), buildTelegramQueueMenuReplyMarkup(menuItems));
|
|
347
371
|
state.mode = "queue";
|
|
348
372
|
await deps.answerCallbackQuery(query.id);
|
|
@@ -354,7 +378,7 @@ function createQueueMenuCallbackHandler(deps) {
|
|
|
354
378
|
return deps.telegramQueueStore.getQueuedItems();
|
|
355
379
|
};
|
|
356
380
|
const toMenuItems = () => {
|
|
357
|
-
return toTelegramQueueMenuItems(getQueueSnapshot());
|
|
381
|
+
return toTelegramQueueMenuItems(getQueueSnapshot(), deps.hasPendingDispatch?.());
|
|
358
382
|
};
|
|
359
383
|
const findItem = (cId, rId) => {
|
|
360
384
|
return findTelegramQueueItem(toMenuItems(), cId, rId);
|
|
@@ -367,6 +391,7 @@ function createQueueMenuCallbackHandler(deps) {
|
|
|
367
391
|
const flagDeps = {
|
|
368
392
|
getQueueSnapshot,
|
|
369
393
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
394
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
370
395
|
};
|
|
371
396
|
return handleTelegramQueueMenuCallback(query.id, data, chatId, messageId, ctx, {
|
|
372
397
|
getQueuedItems: toMenuItems,
|
|
@@ -379,17 +404,8 @@ function createQueueMenuCallbackHandler(deps) {
|
|
|
379
404
|
dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
|
|
380
405
|
});
|
|
381
406
|
},
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
skipped: isQueuedTelegramPromptSkipped(item),
|
|
385
|
-
})),
|
|
386
|
-
setPriority: (cId, rId, enabled) => applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, (item) => ({
|
|
387
|
-
priority: enabled,
|
|
388
|
-
skipped: isQueuedTelegramPromptSkipped(item),
|
|
389
|
-
})),
|
|
390
|
-
setSkipped: (cId, rId, skipped) => applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, (item) => item.kind === "prompt"
|
|
391
|
-
? { priority: item.queueLane === "priority", skipped }
|
|
392
|
-
: undefined),
|
|
407
|
+
setLane: (cId, rId, lane) => applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, () => ({ lane })),
|
|
408
|
+
setSkipped: (cId, rId, skipped) => applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, () => ({ skipped })),
|
|
393
409
|
updateQueueMessage: deps.editInteractiveMessage,
|
|
394
410
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
395
411
|
updateStatus: deps.updateStatus,
|
|
@@ -413,39 +429,26 @@ async function skipQueuedTelegramGuestPrompt(queueOrder, ctx, deps) {
|
|
|
413
429
|
}
|
|
414
430
|
return true;
|
|
415
431
|
}
|
|
416
|
-
|
|
417
|
-
if (priority && skipped) {
|
|
418
|
-
return {
|
|
419
|
-
kind: "priority-suppressed",
|
|
420
|
-
priorityEmoji: item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡",
|
|
421
|
-
suppressionEmoji: item.kind === "prompt" ? (item.reactionSuppressionEmoji ?? "👎") : "👎",
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
if (priority) {
|
|
425
|
-
return {
|
|
426
|
-
kind: "priority",
|
|
427
|
-
emoji: item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡",
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
if (skipped) {
|
|
431
|
-
return {
|
|
432
|
-
kind: "suppressed",
|
|
433
|
-
emoji: item.kind === "prompt" ? (item.reactionSuppressionEmoji ?? "👎") : "👎",
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
return { kind: "default" };
|
|
437
|
-
}
|
|
438
|
-
function isQueuedTelegramPromptSkipped(item) {
|
|
439
|
-
return item.kind === "prompt" && item.reactionSuppressionEmoji !== undefined;
|
|
440
|
-
}
|
|
441
|
-
/** Applies a menu Priority/Skip choice to one queued item through the reaction disposition path. */
|
|
432
|
+
/** Each menu selection changes one dimension through the existing Queue owner. */
|
|
442
433
|
function applyQueuedTelegramPromptMenuFlags(chatId, replyToMessageId, ctx, deps, chooseFlags) {
|
|
443
434
|
const item = findTelegramQueueItem(deps.getQueueSnapshot(), chatId, replyToMessageId);
|
|
444
435
|
const flags = item ? chooseFlags(item) : undefined;
|
|
445
|
-
if (!item || !flags)
|
|
436
|
+
if (!item || item.kind !== "prompt" || !flags)
|
|
446
437
|
return false;
|
|
447
|
-
deps.
|
|
448
|
-
|
|
438
|
+
const view = findTelegramQueueItem(toTelegramQueueMenuItems(deps.getQueueSnapshot(), deps.hasPendingDispatch?.()), chatId, replyToMessageId);
|
|
439
|
+
if (!view || (flags.lane !== undefined && !view.canSelectLane) || (flags.skipped !== undefined && !view.canSelectDisposition))
|
|
440
|
+
return false;
|
|
441
|
+
const disposition = { kind: "reaction-transition" };
|
|
442
|
+
if (flags.lane !== undefined) {
|
|
443
|
+
disposition.promptLane = flags.lane;
|
|
444
|
+
disposition.priorityEmoji = flags.lane === "steer" ? "🔥" : flags.lane === "priority" ?
|
|
445
|
+
getTelegramQueueMenuLaneBadge({ queueLane: "priority", priorityEmoji: item.queueLane === "priority" ? item.priorityEmoji : undefined }) : null;
|
|
446
|
+
}
|
|
447
|
+
if (flags.skipped !== undefined)
|
|
448
|
+
disposition.suppressionEmoji = flags.skipped ? (item.reactionSuppressionEmoji ?? "👎") : null;
|
|
449
|
+
return deps.queueMutationRuntime.applyReactionByMessageId(replyToMessageId, disposition, ctx) ||
|
|
450
|
+
((flags.lane === undefined || flags.lane === item.queueLane) &&
|
|
451
|
+
(flags.skipped === undefined || flags.skipped === (item.reactionSuppressionEmoji !== undefined)));
|
|
449
452
|
}
|
|
450
453
|
function createQueueMenuSendMessageAdapter(sendInteractiveMessage) {
|
|
451
454
|
return (chatId, _replyToMessageId, text, replyMarkup) => {
|
package/dist/lib/pi.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Owns direct pi SDK imports and exposes narrow bridge-facing helpers/types for the extension composition layer
|
|
5
5
|
*/
|
|
6
6
|
import type { AssistantMessageEvent } from "@earendil-works/pi-ai";
|
|
7
|
-
import { type AgentEndEvent, type AgentSettledEvent, type AgentStartEvent, type BeforeAgentStartEvent, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext, type InputEvent, type MessageEndEvent, type SessionBeforeCompactEvent, type SessionCompactEvent, type SessionShutdownEvent, type SessionStartEvent, type SlashCommandInfo, type UIPromptEndEvent, type UIPromptStartEvent } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AssistantMessageEvent, BeforeAgentStartEvent, ExtensionAPI, ExtensionCommandContext, ExtensionContext, InputEvent, MessageEndEvent, SessionBeforeCompactEvent, SessionCompactEvent, SessionShutdownEvent, SessionStartEvent, SlashCommandInfo, UIPromptEndEvent, UIPromptStartEvent, };
|
|
7
|
+
import { type AgentEndEvent, type AgentSettledEvent, type AgentStartEvent, type BeforeAgentStartEvent, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext, type InputEvent, type MessageEndEvent, type SessionBeforeCompactEvent, type SessionCompactEvent, type SessionShutdownEvent, type SessionStartEvent, type SlashCommandInfo, type TurnEndEvent, type TurnEndEventResult, type UIPromptEndEvent, type UIPromptStartEvent } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AssistantMessageEvent, BeforeAgentStartEvent, ExtensionAPI, ExtensionCommandContext, ExtensionContext, InputEvent, MessageEndEvent, SessionBeforeCompactEvent, SessionCompactEvent, SessionShutdownEvent, SessionStartEvent, SlashCommandInfo, TurnEndEvent, TurnEndEventResult, UIPromptEndEvent, UIPromptStartEvent, };
|
|
9
9
|
export interface SessionCompactFailedEvent {
|
|
10
10
|
type: "session_compact_failed";
|
|
11
11
|
reason: "manual" | "threshold" | "overflow";
|
|
@@ -72,5 +72,6 @@ export declare function getExtensionContextCwd(ctx: ExtensionContext): string;
|
|
|
72
72
|
export declare function getExtensionContextSessionId(ctx: ExtensionContext): string;
|
|
73
73
|
export declare function getExtensionContextSessionId(ctx: ExtensionContext | undefined): string | undefined;
|
|
74
74
|
export declare function isExtensionContextIdle(ctx: ExtensionContext): boolean;
|
|
75
|
+
export declare function isExtensionContextAbortRequested(ctx: ExtensionContext): boolean;
|
|
75
76
|
export declare function hasExtensionContextPendingMessages(ctx: ExtensionContext): boolean;
|
|
76
77
|
export declare function compactExtensionContext(ctx: ExtensionContext, callbacks: Parameters<ExtensionContext["compact"]>[0]): ReturnType<ExtensionContext["compact"]>;
|
package/dist/lib/pi.js
CHANGED
|
@@ -122,6 +122,9 @@ export function getExtensionContextSessionId(ctx) {
|
|
|
122
122
|
export function isExtensionContextIdle(ctx) {
|
|
123
123
|
return ctx.isIdle();
|
|
124
124
|
}
|
|
125
|
+
export function isExtensionContextAbortRequested(ctx) {
|
|
126
|
+
return ctx.signal?.aborted === true;
|
|
127
|
+
}
|
|
125
128
|
export function hasExtensionContextPendingMessages(ctx) {
|
|
126
129
|
return ctx.hasPendingMessages();
|
|
127
130
|
}
|
package/dist/lib/polling.js
CHANGED
|
@@ -844,7 +844,9 @@ export function createTelegramThreadCapabilityMonitor(deps) {
|
|
|
844
844
|
consecutiveDisabledProbes = 0;
|
|
845
845
|
const botState = deps.topicTargetStore.getBotState();
|
|
846
846
|
const current = botState.threadMode;
|
|
847
|
-
|
|
847
|
+
// Persisted capability is not proof that this owner started its local bus.
|
|
848
|
+
if (threadModeEnabled && current === "enabled" &&
|
|
849
|
+
(deps.getPollingStartedWithTelegramBus() || !deps.ownsLock(ctx)))
|
|
848
850
|
return;
|
|
849
851
|
if (!threadModeEnabled && current === "disabled") {
|
|
850
852
|
if (!deps.ownsLock(ctx) ||
|
package/dist/lib/queue.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram queue core contracts and pure planning helpers
|
|
3
3
|
* Zones: telegram queue, pi agent lifecycle, scheduling
|
|
4
|
-
* Owns queue item contracts, lane admission,
|
|
4
|
+
* Owns queue item contracts, lane admission, mutations, dispatch planning, and protected Steer publication attempts
|
|
5
5
|
*/
|
|
6
|
+
import { type TurnEndEvent, type TurnEndEventResult } from "./pi.ts";
|
|
6
7
|
export interface QueuedAttachment {
|
|
7
8
|
path: string;
|
|
8
9
|
fileName: string;
|
|
@@ -18,7 +19,7 @@ export interface TelegramPromptImageContent {
|
|
|
18
19
|
}
|
|
19
20
|
export type TelegramPromptContent = TelegramPromptTextContent | TelegramPromptImageContent;
|
|
20
21
|
export type TelegramQueueItemKind = "prompt" | "control";
|
|
21
|
-
export type TelegramQueueLane = "control" | "priority" | "default";
|
|
22
|
+
export type TelegramQueueLane = "control" | "steer" | "priority" | "default";
|
|
22
23
|
export type TelegramQueueReactionDisposition = {
|
|
23
24
|
kind: "default";
|
|
24
25
|
} | {
|
|
@@ -33,6 +34,7 @@ export type TelegramQueueReactionDisposition = {
|
|
|
33
34
|
suppressionEmoji: string;
|
|
34
35
|
} | {
|
|
35
36
|
kind: "reaction-transition";
|
|
37
|
+
promptLane?: Exclude<TelegramQueueLane, "control">;
|
|
36
38
|
priorityEmoji?: string | null;
|
|
37
39
|
suppressionEmoji?: string | null;
|
|
38
40
|
};
|
|
@@ -42,7 +44,7 @@ export interface TelegramQueueAdmissionReceipt {
|
|
|
42
44
|
sourceUpdateIds: readonly number[];
|
|
43
45
|
journalBindingKey?: string;
|
|
44
46
|
}
|
|
45
|
-
export type TelegramQueueAdmissionMode = "control-queue" | "priority-queue" | "default-queue";
|
|
47
|
+
export type TelegramQueueAdmissionMode = "control-queue" | "steer-queue" | "priority-queue" | "default-queue";
|
|
46
48
|
export interface TelegramQueueLaneContract {
|
|
47
49
|
lane: TelegramQueueLane;
|
|
48
50
|
admissionMode: TelegramQueueAdmissionMode;
|
|
@@ -84,6 +86,11 @@ export interface PendingTelegramTurn extends TelegramQueueItemBase {
|
|
|
84
86
|
historyText: string;
|
|
85
87
|
priorityEmoji?: string;
|
|
86
88
|
reactionSuppressionEmoji?: string;
|
|
89
|
+
/** Frozen before receipt mutation; failed/partial attempts must never become executable again. */
|
|
90
|
+
steerAttempted?: true;
|
|
91
|
+
/** Original tail objects of an issued idle packet; metadata only, not another waiting queue. */
|
|
92
|
+
steerPacketTail?: readonly PendingTelegramTurn[];
|
|
93
|
+
steerPacketSourceOrders?: readonly number[];
|
|
87
94
|
/** Emit the explicit aborted-turn notice when /next settles this active turn. */
|
|
88
95
|
announceNextAbortOnEnd?: boolean;
|
|
89
96
|
/** Turn should preferably be delivered as voice (mirror mode + user sent voice) */
|
|
@@ -220,8 +227,11 @@ export declare function appendTelegramQueueItem<TContext = unknown, TItem extend
|
|
|
220
227
|
export declare function createTelegramQueueHandoff<TContext>(input: {
|
|
221
228
|
handoffToken: string;
|
|
222
229
|
item: TelegramQueueItem<TContext>;
|
|
230
|
+
steerSupported?: boolean;
|
|
223
231
|
}): TelegramQueueHandoff;
|
|
224
|
-
export declare function createTelegramQueueHandoffPayload<TContext>(item: TelegramQueueItem<TContext
|
|
232
|
+
export declare function createTelegramQueueHandoffPayload<TContext>(item: TelegramQueueItem<TContext>, options?: {
|
|
233
|
+
steerSupported?: boolean;
|
|
234
|
+
}): TelegramQueueHandoffPayload;
|
|
225
235
|
export declare function restoreTelegramQueueHandoffPayload<TContext>(payload: TelegramQueueHandoffPayload, createControlExecution: (payload: TelegramControlQueueHandoffPayload) => PendingTelegramControlItem<TContext>["execute"]): TelegramQueueItem<TContext>;
|
|
226
236
|
export declare function removeTelegramQueueItemByReceipt<TContext>(input: {
|
|
227
237
|
receipt: TelegramQueueAdmissionReceipt;
|
|
@@ -258,7 +268,6 @@ export declare function consumeDispatchedTelegramPrompt<TContext = unknown>(item
|
|
|
258
268
|
activeTurn?: PendingTelegramTurn;
|
|
259
269
|
remainingItems: TelegramQueueItem<TContext>[];
|
|
260
270
|
};
|
|
261
|
-
export declare function formatQueuedTelegramItemsStatus<TContext = unknown>(items: TelegramQueueItem<TContext>[]): string;
|
|
262
271
|
export declare function truncateTelegramQueueSummary(text: string, maxWords?: number, maxLength?: number): string;
|
|
263
272
|
export declare function canDispatchTelegramTurnState(state: TelegramDispatchGuardState): boolean;
|
|
264
273
|
export interface TelegramDispatchReadinessDeps<TContext> {
|
|
@@ -296,6 +305,11 @@ export declare function createTelegramControlItemBuilder<TContext = unknown>(dep
|
|
|
296
305
|
export type TelegramQueueDispatchAction<TContext = unknown> = {
|
|
297
306
|
kind: "none";
|
|
298
307
|
remainingItems: TelegramQueueItem<TContext>[];
|
|
308
|
+
} | {
|
|
309
|
+
kind: "steer";
|
|
310
|
+
item: PendingTelegramTurn;
|
|
311
|
+
packet: readonly PendingTelegramTurn[];
|
|
312
|
+
remainingItems: TelegramQueueItem<TContext>[];
|
|
299
313
|
} | {
|
|
300
314
|
kind: "control";
|
|
301
315
|
item: PendingTelegramControlItem<TContext>;
|
|
@@ -305,6 +319,38 @@ export type TelegramQueueDispatchAction<TContext = unknown> = {
|
|
|
305
319
|
item: PendingTelegramTurn;
|
|
306
320
|
remainingItems: TelegramQueueItem<TContext>[];
|
|
307
321
|
};
|
|
322
|
+
export type TelegramSteerPacketPlan = {
|
|
323
|
+
kind: "none";
|
|
324
|
+
} | {
|
|
325
|
+
kind: "skip";
|
|
326
|
+
item: PendingTelegramTurn;
|
|
327
|
+
} | {
|
|
328
|
+
kind: "packet";
|
|
329
|
+
items: readonly PendingTelegramTurn[];
|
|
330
|
+
};
|
|
331
|
+
/** Plan a fixed ready prefix; publication/settlement remain with the Queue effect shell. */
|
|
332
|
+
export declare function planTelegramSteerPacket<TContext>(items: readonly TelegramQueueItem<TContext>[], options: {
|
|
333
|
+
blocked: boolean;
|
|
334
|
+
isItemReady: (item: TelegramQueueItem<TContext>) => boolean;
|
|
335
|
+
canAppend?: (packet: readonly PendingTelegramTurn[], item: PendingTelegramTurn) => boolean;
|
|
336
|
+
}): TelegramSteerPacketPlan;
|
|
337
|
+
export declare function isTelegramSteerItemWithinIdleCapacity(item: PendingTelegramTurn): boolean;
|
|
338
|
+
export interface TelegramSteerBoundaryRuntimeDeps<TContext> extends TelegramQueueStore<TContext> {
|
|
339
|
+
isContextCurrent: (ctx: TContext) => boolean;
|
|
340
|
+
isBlocked: (ctx: TContext) => boolean;
|
|
341
|
+
getGeneration: () => number;
|
|
342
|
+
isItemReady: (item: TelegramQueueItem<TContext>, ctx: TContext) => boolean;
|
|
343
|
+
isItemCurrent: (item: PendingTelegramTurn, ctx: TContext) => boolean;
|
|
344
|
+
getActiveTurn: () => PendingTelegramTurn | undefined;
|
|
345
|
+
commitPacket: (items: readonly PendingTelegramTurn[], ctx: TContext) => boolean;
|
|
346
|
+
discardItem: (item: PendingTelegramTurn, ctx: TContext) => boolean;
|
|
347
|
+
updateStatus: (ctx: TContext) => void;
|
|
348
|
+
recordRuntimeEvent?: TelegramRuntimeEventRecorderPort["recordRuntimeEvent"];
|
|
349
|
+
}
|
|
350
|
+
/** One publication attempt; journal ACK is source disposition, never SDK/model-use confirmation. */
|
|
351
|
+
export declare function createTelegramSteerBoundaryRuntime<TContext>(deps: TelegramSteerBoundaryRuntimeDeps<TContext>): {
|
|
352
|
+
onTurnEnd(event: Pick<TurnEndEvent, "turnIndex" | "outcome">, ctx: TContext): TurnEndEventResult | undefined;
|
|
353
|
+
};
|
|
308
354
|
export declare function planNextTelegramQueueAction<TContext = unknown>(items: TelegramQueueItem<TContext>[], canDispatch: boolean): TelegramQueueDispatchAction<TContext>;
|
|
309
355
|
export declare function shouldDispatchAfterTelegramAgentEnd(options: {
|
|
310
356
|
hasTurn: boolean;
|
|
@@ -752,6 +798,12 @@ export interface TelegramQueueDispatchControllerDeps<TContext = unknown> extends
|
|
|
752
798
|
sendUserMessage: TelegramDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
753
799
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
754
800
|
reconcileNextDispatchAnnouncementReplyOwnership?: (item: PendingTelegramTurn) => void;
|
|
801
|
+
steerDispatch?: {
|
|
802
|
+
commitPacket: (items: readonly PendingTelegramTurn[], ctx: TContext) => boolean;
|
|
803
|
+
isContextCurrent?: (ctx: TContext) => boolean;
|
|
804
|
+
/** Rechecks idle/native/abort guards without rejecting this packet's own pending flag. */
|
|
805
|
+
canSubmit?: (ctx: TContext) => boolean;
|
|
806
|
+
};
|
|
755
807
|
isQueueItemTransportActive?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
756
808
|
hasPendingInboundQueueMutationForItem?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
757
809
|
isQueueItemAdmissionReady?: (item: TelegramQueueItem<TContext>) => boolean;
|