@llblab/pi-telegram 0.55.0 → 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 +14 -0
- package/README.md +27 -18
- 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 +5 -1
- package/dist/lib/bus-leader.js +9 -1
- package/dist/lib/bus.d.ts +5 -0
- package/dist/lib/bus.js +17 -6
- 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/routing.d.ts +1 -1
- package/dist/lib/routing.js +261 -29
- package/dist/lib/status.d.ts +1 -2
- package/dist/lib/status.js +6 -7
- package/dist/lib/turns.d.ts +4 -0
- package/dist/lib/turns.js +6 -0
- package/dist/lib/updates.d.ts +10 -6
- package/dist/lib/updates.js +20 -4
- package/dist/package.json +1 -1
- package/docs/README.md +1 -1
- package/docs/architecture.md +17 -14
- package/docs/callback-namespaces.md +4 -0
- package/docs/multi-instance-bus.md +4 -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 +20 -3
- 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/routing.ts +299 -29
- package/lib/status.ts +7 -7
- package/lib/turns.ts +7 -0
- package/lib/updates.ts +24 -10
- package/package.json +1 -1
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;
|
package/dist/lib/queue.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { isPiStaleContextError } from "./pi.js";
|
|
@@ -18,16 +18,22 @@ export const TELEGRAM_QUEUE_LANE_CONTRACTS = [
|
|
|
18
18
|
dispatchRank: 0,
|
|
19
19
|
allowedKinds: ["control", "prompt"],
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
lane: "steer",
|
|
23
|
+
admissionMode: "steer-queue",
|
|
24
|
+
dispatchRank: 1,
|
|
25
|
+
allowedKinds: ["prompt"],
|
|
26
|
+
},
|
|
21
27
|
{
|
|
22
28
|
lane: "priority",
|
|
23
29
|
admissionMode: "priority-queue",
|
|
24
|
-
dispatchRank:
|
|
30
|
+
dispatchRank: 2,
|
|
25
31
|
allowedKinds: ["prompt"],
|
|
26
32
|
},
|
|
27
33
|
{
|
|
28
34
|
lane: "default",
|
|
29
35
|
admissionMode: "default-queue",
|
|
30
|
-
dispatchRank:
|
|
36
|
+
dispatchRank: 3,
|
|
31
37
|
allowedKinds: ["prompt"],
|
|
32
38
|
},
|
|
33
39
|
];
|
|
@@ -59,7 +65,8 @@ function parseQueuePayloadReceipt(value, kind) {
|
|
|
59
65
|
/** Data decoder shared by authenticated IPC and cold storage; decoding grants no admission. */
|
|
60
66
|
export function parseTelegramQueueHandoffPayload(value) {
|
|
61
67
|
if (!isQueuePayloadRecord(value) ||
|
|
62
|
-
(value.kind !== "prompt" && value.kind !== "control")
|
|
68
|
+
(value.kind !== "prompt" && value.kind !== "control") ||
|
|
69
|
+
value.steerAttempted !== undefined || value.steerPacketTail !== undefined || value.steerPacketSourceOrders !== undefined)
|
|
63
70
|
return undefined;
|
|
64
71
|
const target = isQueuePayloadRecord(value.target) &&
|
|
65
72
|
typeof value.target.chatId === "number"
|
|
@@ -88,6 +95,7 @@ export function parseTelegramQueueHandoffPayload(value) {
|
|
|
88
95
|
typeof value.guestInlineMessageId !== "string") ||
|
|
89
96
|
!Number.isSafeInteger(value.queueOrder) ||
|
|
90
97
|
(value.queueLane !== "control" &&
|
|
98
|
+
value.queueLane !== "steer" &&
|
|
91
99
|
value.queueLane !== "priority" &&
|
|
92
100
|
value.queueLane !== "default") ||
|
|
93
101
|
!Number.isSafeInteger(value.laneOrder) ||
|
|
@@ -123,6 +131,7 @@ export function parseTelegramQueueHandoffPayload(value) {
|
|
|
123
131
|
: undefined;
|
|
124
132
|
}
|
|
125
133
|
if (value.queueLane === "control" ||
|
|
134
|
+
(value.queueLane === "steer" && (value.guestQueryId !== undefined || value.guestInlineMessageId !== undefined)) ||
|
|
126
135
|
!Array.isArray(value.sourceMessageIds) ||
|
|
127
136
|
value.sourceMessageIds.some((id) => !Number.isSafeInteger(id)) ||
|
|
128
137
|
!Array.isArray(value.queuedAttachments) ||
|
|
@@ -282,7 +291,8 @@ function isTelegramQueueItemSkipped(item) {
|
|
|
282
291
|
return item.kind === "prompt" && Boolean(item.reactionSuppressionEmoji);
|
|
283
292
|
}
|
|
284
293
|
export function countExecutableTelegramQueueItems(items) {
|
|
285
|
-
return items.filter((item) => !isTelegramQueueItemSkipped(item)
|
|
294
|
+
return items.filter((item) => !isTelegramQueueItemSkipped(item) &&
|
|
295
|
+
!(item.kind === "prompt" && item.steerAttempted)).length;
|
|
286
296
|
}
|
|
287
297
|
export function createTelegramQueueItemCountGetter(store) {
|
|
288
298
|
return () => countExecutableTelegramQueueItems(store.getQueuedItems());
|
|
@@ -349,7 +359,7 @@ export function partitionTelegramQueueItemsForHistory(items) {
|
|
|
349
359
|
const historyTurns = [];
|
|
350
360
|
const remainingItems = [];
|
|
351
361
|
for (const item of items) {
|
|
352
|
-
if (isPendingTelegramTurn(item)) {
|
|
362
|
+
if (isPendingTelegramTurn(item) && item.queueLane !== "steer" && !item.steerAttempted) {
|
|
353
363
|
historyTurns.push(item);
|
|
354
364
|
continue;
|
|
355
365
|
}
|
|
@@ -417,7 +427,7 @@ export function createTelegramQueueHandoff(input) {
|
|
|
417
427
|
}
|
|
418
428
|
const handoff = {
|
|
419
429
|
handoffToken: input.handoffToken,
|
|
420
|
-
payload: createTelegramQueueHandoffPayload(input.item),
|
|
430
|
+
payload: createTelegramQueueHandoffPayload(input.item, { steerSupported: input.steerSupported }),
|
|
421
431
|
};
|
|
422
432
|
if (Buffer.byteLength(JSON.stringify(handoff)) >
|
|
423
433
|
TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
|
|
@@ -425,8 +435,17 @@ export function createTelegramQueueHandoff(input) {
|
|
|
425
435
|
}
|
|
426
436
|
return handoff;
|
|
427
437
|
}
|
|
428
|
-
export function createTelegramQueueHandoffPayload(item) {
|
|
438
|
+
export function createTelegramQueueHandoffPayload(item, options = {}) {
|
|
429
439
|
assertTelegramQueueItemAdmissionValid(item);
|
|
440
|
+
if (item.kind === "prompt" && (item.steerAttempted || item.steerPacketTail !== undefined || item.steerPacketSourceOrders !== undefined)) {
|
|
441
|
+
throw new Error("Attempted Steer work cannot be transferred.");
|
|
442
|
+
}
|
|
443
|
+
if (item.queueLane === "steer" && (item.guestQueryId !== undefined || item.guestInlineMessageId !== undefined)) {
|
|
444
|
+
throw new Error("Guest prompts cannot use Steer queue transfer.");
|
|
445
|
+
}
|
|
446
|
+
if (item.queueLane === "steer" && options.steerSupported !== true) {
|
|
447
|
+
throw new Error("Steer queue transfer requires compatible lane support.");
|
|
448
|
+
}
|
|
430
449
|
if (!item.admissionReceipts?.length) {
|
|
431
450
|
throw new Error("Telegram queue handoff requires durable admission receipts.");
|
|
432
451
|
}
|
|
@@ -639,6 +658,7 @@ export function removeTelegramQueueItemsByMessageIds(items, messageIds, scope) {
|
|
|
639
658
|
const removedItems = [];
|
|
640
659
|
for (const item of items) {
|
|
641
660
|
const shouldRemove = isPendingTelegramTurn(item) &&
|
|
661
|
+
!item.steerAttempted &&
|
|
642
662
|
isTelegramQueueItemInMessageScope(item, scope) &&
|
|
643
663
|
item.sourceMessageIds.some((messageId) => deletedMessageIds.has(messageId));
|
|
644
664
|
(shouldRemove ? removedItems : nextItems).push(item);
|
|
@@ -665,9 +685,15 @@ function removeTelegramQueuedGuestPromptByOrder(items, queueOrder) {
|
|
|
665
685
|
};
|
|
666
686
|
}
|
|
667
687
|
export function applyTelegramQueuePromptReactionDisposition(items, messageId, disposition, destinationLaneOrder, scope) {
|
|
688
|
+
if (disposition.kind === "reaction-transition" &&
|
|
689
|
+
disposition.promptLane !== undefined &&
|
|
690
|
+
!["steer", "priority", "default"].includes(disposition.promptLane)) {
|
|
691
|
+
throw new Error("Telegram prompt lane selection is invalid.");
|
|
692
|
+
}
|
|
668
693
|
let nextItems = items;
|
|
669
694
|
for (const [index, item] of items.entries()) {
|
|
670
695
|
if (!isPendingTelegramTurn(item) ||
|
|
696
|
+
item.steerAttempted ||
|
|
671
697
|
item.queueLane === "control" ||
|
|
672
698
|
!isTelegramQueueItemInMessageScope(item, scope) ||
|
|
673
699
|
!item.sourceMessageIds.includes(messageId)) {
|
|
@@ -679,7 +705,12 @@ export function applyTelegramQueuePromptReactionDisposition(items, messageId, di
|
|
|
679
705
|
: disposition.priorityEmoji !== null
|
|
680
706
|
: disposition.kind === "priority" ||
|
|
681
707
|
disposition.kind === "priority-suppressed";
|
|
682
|
-
const queueLane =
|
|
708
|
+
const queueLane = disposition.kind === "reaction-transition"
|
|
709
|
+
? (disposition.promptLane ??
|
|
710
|
+
(disposition.priorityEmoji === undefined
|
|
711
|
+
? item.queueLane
|
|
712
|
+
: isPriority ? "priority" : "default"))
|
|
713
|
+
: isPriority ? "priority" : "default";
|
|
683
714
|
const laneOrder = item.queueLane === queueLane ? item.laneOrder : destinationLaneOrder;
|
|
684
715
|
if (laneOrder === undefined) {
|
|
685
716
|
throw new Error("Telegram destination lane order is unavailable.");
|
|
@@ -728,12 +759,13 @@ export function consumeDispatchedTelegramPrompt(items, hasPendingDispatch) {
|
|
|
728
759
|
if (!nextItem || !isPendingTelegramTurn(nextItem)) {
|
|
729
760
|
return { activeTurn: undefined, remainingItems: items };
|
|
730
761
|
}
|
|
762
|
+
if (nextItem.steerPacketTail !== undefined) {
|
|
763
|
+
const packet = [nextItem, ...nextItem.steerPacketTail];
|
|
764
|
+
const originals = new Set(packet);
|
|
765
|
+
return { activeTurn: projectTelegramSteerPacketTurn(packet), remainingItems: items.filter(item => !originals.has(item)) };
|
|
766
|
+
}
|
|
731
767
|
return { activeTurn: nextItem, remainingItems: items.slice(1) };
|
|
732
768
|
}
|
|
733
|
-
export function formatQueuedTelegramItemsStatus(items) {
|
|
734
|
-
const count = countExecutableTelegramQueueItems(items);
|
|
735
|
-
return count === 0 ? "" : ` +${count}`;
|
|
736
|
-
}
|
|
737
769
|
export function truncateTelegramQueueSummary(text, maxWords = 5, maxLength = 40) {
|
|
738
770
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
739
771
|
if (!normalized)
|
|
@@ -789,6 +821,170 @@ export function createTelegramControlItemBuilder(deps) {
|
|
|
789
821
|
laneOrder: deps.allocateControlOrder(),
|
|
790
822
|
});
|
|
791
823
|
}
|
|
824
|
+
/** Plan a fixed ready prefix; publication/settlement remain with the Queue effect shell. */
|
|
825
|
+
export function planTelegramSteerPacket(items, options) {
|
|
826
|
+
if (options.blocked)
|
|
827
|
+
return { kind: "none" };
|
|
828
|
+
const ordered = [...items].sort(compareTelegramQueueItems);
|
|
829
|
+
if (ordered.some((item) => item.queueLane === "control" && options.isItemReady(item))) {
|
|
830
|
+
return { kind: "none" };
|
|
831
|
+
}
|
|
832
|
+
const packet = [];
|
|
833
|
+
for (const item of ordered) {
|
|
834
|
+
if (item.queueLane !== "steer" || !isPendingTelegramTurn(item))
|
|
835
|
+
continue;
|
|
836
|
+
if (item.steerAttempted || !options.isItemReady(item) || item.guestQueryId !== undefined || item.guestInlineMessageId !== undefined)
|
|
837
|
+
break;
|
|
838
|
+
if (item.reactionSuppressionEmoji) {
|
|
839
|
+
if (packet.length === 0)
|
|
840
|
+
return { kind: "skip", item };
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
const first = packet[0];
|
|
844
|
+
if (first && !isSameTelegramPromptNamespace(first, item))
|
|
845
|
+
break;
|
|
846
|
+
if (options.canAppend && !options.canAppend(packet, item))
|
|
847
|
+
break;
|
|
848
|
+
packet.push(item);
|
|
849
|
+
}
|
|
850
|
+
return packet.length > 0 ? { kind: "packet", items: packet } : { kind: "none" };
|
|
851
|
+
}
|
|
852
|
+
function buildTelegramSteerPacketContent(packet, idleTask = false) {
|
|
853
|
+
const content = packet.flatMap((item, index) => [
|
|
854
|
+
...(index > 0 ? [{ type: "text", text: "\n\n" }] : []), ...item.content,
|
|
855
|
+
]);
|
|
856
|
+
// Ordinary idle prompts must retain origin detection by before_agent_start.
|
|
857
|
+
return idleTask ? [{ type: "text", text: "[telegram]\n" }, ...content] : content;
|
|
858
|
+
}
|
|
859
|
+
function createTelegramSteerPacketBudget(idleTask = false) {
|
|
860
|
+
let packetBytes = idleTask ? 64 : 0;
|
|
861
|
+
return (packet, item) => {
|
|
862
|
+
const bytes = Buffer.byteLength(JSON.stringify(item.content)) +
|
|
863
|
+
(packet.length > 0 ? Buffer.byteLength("\n\n") : 0) + 64;
|
|
864
|
+
if (packetBytes + bytes > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES)
|
|
865
|
+
return false;
|
|
866
|
+
packetBytes += bytes;
|
|
867
|
+
return true;
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
export function isTelegramSteerItemWithinIdleCapacity(item) {
|
|
871
|
+
return createTelegramSteerPacketBudget(true)([], item);
|
|
872
|
+
}
|
|
873
|
+
function projectTelegramSteerPacketTurn(packet) {
|
|
874
|
+
const first = packet[0];
|
|
875
|
+
if (!first)
|
|
876
|
+
throw new Error("Steer packet cannot be empty.");
|
|
877
|
+
const projected = {
|
|
878
|
+
...first,
|
|
879
|
+
content: buildTelegramSteerPacketContent(packet, true),
|
|
880
|
+
historyText: packet.map(item => item.historyText).join("\n\n"),
|
|
881
|
+
sourceMessageIds: [...new Set(packet.flatMap(item => item.sourceMessageIds))],
|
|
882
|
+
queuedAttachments: packet.flatMap(item => item.queuedAttachments),
|
|
883
|
+
admissionReceipts: packet.flatMap(item => item.admissionReceipts ?? []),
|
|
884
|
+
voiceReplyPreferred: packet.some(item => item.voiceReplyPreferred === true),
|
|
885
|
+
voiceReplyRequired: packet.some(item => item.voiceReplyRequired === true),
|
|
886
|
+
steerPacketSourceOrders: packet.map(item => item.queueOrder),
|
|
887
|
+
};
|
|
888
|
+
delete projected.steerPacketTail;
|
|
889
|
+
return projected;
|
|
890
|
+
}
|
|
891
|
+
function isSameTelegramPromptNamespace(left, right) {
|
|
892
|
+
return left.chatId === right.chatId &&
|
|
893
|
+
left.target?.chatId === right.target?.chatId &&
|
|
894
|
+
left.target?.threadId === right.target?.threadId &&
|
|
895
|
+
left.transportStamp?.profile === right.transportStamp?.profile &&
|
|
896
|
+
left.transportStamp?.generation === right.transportStamp?.generation;
|
|
897
|
+
}
|
|
898
|
+
/** One publication attempt; journal ACK is source disposition, never SDK/model-use confirmation. */
|
|
899
|
+
export function createTelegramSteerBoundaryRuntime(deps) {
|
|
900
|
+
const updateStatus = (ctx) => {
|
|
901
|
+
try {
|
|
902
|
+
if (deps.isContextCurrent(ctx))
|
|
903
|
+
deps.updateStatus(ctx);
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
if (!isPiStaleContextError(error)) {
|
|
907
|
+
try {
|
|
908
|
+
deps.recordRuntimeEvent?.("queue", error, { phase: "steer-status" });
|
|
909
|
+
}
|
|
910
|
+
catch { /* Diagnostic failure cannot replay input. */ }
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
const report = (error, phase, ctx) => {
|
|
915
|
+
try {
|
|
916
|
+
deps.recordRuntimeEvent?.("queue", error, { phase });
|
|
917
|
+
}
|
|
918
|
+
catch { /* Diagnostics cannot replay input. */ }
|
|
919
|
+
updateStatus(ctx);
|
|
920
|
+
};
|
|
921
|
+
return {
|
|
922
|
+
onTurnEnd(event, ctx) {
|
|
923
|
+
const generation = deps.getGeneration();
|
|
924
|
+
const current = () => deps.isContextCurrent(ctx) && generation === deps.getGeneration();
|
|
925
|
+
const blocked = () => !current() || event.outcome !== "completed" || deps.isBlocked(ctx);
|
|
926
|
+
try {
|
|
927
|
+
if (blocked())
|
|
928
|
+
return;
|
|
929
|
+
const matchesActive = (item) => {
|
|
930
|
+
const active = deps.getActiveTurn();
|
|
931
|
+
return item.kind !== "prompt" || !active || isSameTelegramPromptNamespace(active, item);
|
|
932
|
+
};
|
|
933
|
+
const ready = (item) => deps.isItemReady(item, ctx) && matchesActive(item);
|
|
934
|
+
// Skipped heads settle under the same exact owner before recapturing a fresh packet.
|
|
935
|
+
const budget = deps.getQueuedItems().length;
|
|
936
|
+
for (let index = 0; index <= budget; index++) {
|
|
937
|
+
const plan = planTelegramSteerPacket(deps.getQueuedItems(), {
|
|
938
|
+
blocked: blocked(), isItemReady: ready, canAppend: createTelegramSteerPacketBudget(),
|
|
939
|
+
});
|
|
940
|
+
if (plan.kind === "none")
|
|
941
|
+
return;
|
|
942
|
+
if (plan.kind === "skip") {
|
|
943
|
+
if (blocked() || !deps.discardItem(plan.item, ctx))
|
|
944
|
+
return;
|
|
945
|
+
if (!current())
|
|
946
|
+
return;
|
|
947
|
+
deps.setQueuedItems(deps.getQueuedItems().filter(item => item !== plan.item));
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
const packet = plan.items;
|
|
951
|
+
const content = buildTelegramSteerPacketContent(packet);
|
|
952
|
+
const draft = {
|
|
953
|
+
type: "custom_message", customType: "telegram-steer", content, display: true,
|
|
954
|
+
details: { boundaryTurnIndex: event.turnIndex, queueOrders: packet.map(item => item.queueOrder) },
|
|
955
|
+
};
|
|
956
|
+
if (packet.some(item => !ready(item)) ||
|
|
957
|
+
packet.some(item => !deps.getQueuedItems().includes(item)) || blocked())
|
|
958
|
+
return;
|
|
959
|
+
// Keep the same Queue objects/receipts: a refused prefix cannot become a new attempt.
|
|
960
|
+
for (const item of packet)
|
|
961
|
+
item.steerAttempted = true;
|
|
962
|
+
if (!deps.commitPacket(packet, ctx)) {
|
|
963
|
+
report(new Error("Steer packet receipt settlement is unconfirmed."), "steer-settlement-unconfirmed", ctx);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
if (blocked() || packet.some(item => !matchesActive(item) ||
|
|
967
|
+
!deps.isItemCurrent(item, ctx) || !deps.getQueuedItems().includes(item))) {
|
|
968
|
+
report(new Error("Steer packet authority changed before publication."), "steer-publication-refused", ctx);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const selected = new Set(packet);
|
|
972
|
+
deps.setQueuedItems(deps.getQueuedItems().filter(item => !selected.has(item)));
|
|
973
|
+
updateStatus(ctx);
|
|
974
|
+
if (!current() || deps.isBlocked(ctx)) {
|
|
975
|
+
report(new Error("Steer publication was refused after source disposition."), "steer-publication-refused", ctx);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
return { entries: [draft], continue: true };
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
catch (error) {
|
|
982
|
+
report(error, "steer-attempt-failed", ctx);
|
|
983
|
+
}
|
|
984
|
+
return undefined;
|
|
985
|
+
},
|
|
986
|
+
};
|
|
987
|
+
}
|
|
792
988
|
export function planNextTelegramQueueAction(items, canDispatch) {
|
|
793
989
|
if (!canDispatch || items.length === 0) {
|
|
794
990
|
return { kind: "none", remainingItems: items };
|
|
@@ -799,6 +995,11 @@ export function planNextTelegramQueueAction(items, canDispatch) {
|
|
|
799
995
|
}
|
|
800
996
|
assertTelegramQueueItemAdmissionValid(firstItem);
|
|
801
997
|
if (isPendingTelegramTurn(firstItem)) {
|
|
998
|
+
if (firstItem.steerAttempted)
|
|
999
|
+
return { kind: "none", remainingItems: items };
|
|
1000
|
+
// Steer needs whole-packet idle/boundary dispatch, never this single-prompt fallback.
|
|
1001
|
+
if (firstItem.queueLane === "steer")
|
|
1002
|
+
return { kind: "none", remainingItems: items };
|
|
802
1003
|
return { kind: "prompt", item: firstItem, remainingItems: items };
|
|
803
1004
|
}
|
|
804
1005
|
return { kind: "control", item: firstItem, remainingItems };
|
|
@@ -1697,7 +1898,12 @@ export function removeTelegramQueueItemsByMessageIdsRuntime(messageIds, deps, sc
|
|
|
1697
1898
|
return removedCount;
|
|
1698
1899
|
}
|
|
1699
1900
|
function removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, deps) {
|
|
1700
|
-
const
|
|
1901
|
+
const queuedItems = deps.getQueuedItems();
|
|
1902
|
+
const head = queuedItems[0];
|
|
1903
|
+
// agent_start must consume the issued guest, not adopt the next waiting reply owner.
|
|
1904
|
+
if (deps.hasPendingDispatch?.() && head?.queueOrder === queueOrder)
|
|
1905
|
+
return false;
|
|
1906
|
+
const { items, removedItems, removedCount } = removeTelegramQueuedGuestPromptByOrder(queuedItems, queueOrder);
|
|
1701
1907
|
if (removedCount === 0)
|
|
1702
1908
|
return false;
|
|
1703
1909
|
// A menu Skip means permanent discard, so settle durable admission before
|
|
@@ -1709,6 +1915,10 @@ function removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, deps) {
|
|
|
1709
1915
|
}
|
|
1710
1916
|
export function applyTelegramQueuePromptReactionDispositionRuntime(messageId, disposition, deps, scope) {
|
|
1711
1917
|
const queuedItems = deps.getQueuedItems();
|
|
1918
|
+
const head = queuedItems[0];
|
|
1919
|
+
if (deps.hasPendingDispatch?.() && head?.kind === "prompt" &&
|
|
1920
|
+
isTelegramQueueItemInMessageScope(head, scope) && head.sourceMessageIds.includes(messageId))
|
|
1921
|
+
return false;
|
|
1712
1922
|
const suppression = disposition.kind === "reaction-transition"
|
|
1713
1923
|
? disposition.suppressionEmoji
|
|
1714
1924
|
: disposition.kind === "suppressed"
|
|
@@ -1736,17 +1946,16 @@ export function applyTelegramQueuePromptReactionDispositionRuntime(messageId, di
|
|
|
1736
1946
|
}
|
|
1737
1947
|
const changesLane = queuedItems.some((item) => {
|
|
1738
1948
|
if (!isPendingTelegramTurn(item) ||
|
|
1949
|
+
item.steerAttempted ||
|
|
1739
1950
|
item.queueLane === "control" ||
|
|
1740
1951
|
!isTelegramQueueItemInMessageScope(item, scope) ||
|
|
1741
1952
|
!item.sourceMessageIds.includes(messageId)) {
|
|
1742
1953
|
return false;
|
|
1743
1954
|
}
|
|
1744
1955
|
const queueLane = disposition.kind === "reaction-transition"
|
|
1745
|
-
? disposition.priorityEmoji === undefined
|
|
1956
|
+
? (disposition.promptLane ?? (disposition.priorityEmoji === undefined
|
|
1746
1957
|
? item.queueLane
|
|
1747
|
-
: disposition.priorityEmoji === null
|
|
1748
|
-
? "default"
|
|
1749
|
-
: "priority"
|
|
1958
|
+
: disposition.priorityEmoji === null ? "default" : "priority"))
|
|
1750
1959
|
: disposition.kind === "priority" ||
|
|
1751
1960
|
disposition.kind === "priority-suppressed"
|
|
1752
1961
|
? "priority"
|
|
@@ -1779,6 +1988,8 @@ export async function enqueueTelegramPromptTurnRuntime(messages, deps) {
|
|
|
1779
1988
|
const remainingItems = deps.getQueuedItems().filter((item, index) => {
|
|
1780
1989
|
if ((pendingDispatch && index === 0) ||
|
|
1781
1990
|
!isPendingTelegramTurn(item) ||
|
|
1991
|
+
item.steerAttempted ||
|
|
1992
|
+
item.queueLane === "steer" ||
|
|
1782
1993
|
!historyOrders.has(item.queueOrder))
|
|
1783
1994
|
return true;
|
|
1784
1995
|
historyTurns.push(item);
|
|
@@ -1957,6 +2168,8 @@ export function executeTelegramQueueDispatchPlan(plan, deps) {
|
|
|
1957
2168
|
deps.executeControlItem(plan.item);
|
|
1958
2169
|
return;
|
|
1959
2170
|
}
|
|
2171
|
+
if (plan.kind === "steer")
|
|
2172
|
+
throw new Error("Steer packet requires dedicated Queue dispatch.");
|
|
1960
2173
|
deps.onPromptDispatchStart(plan.item.chatId, plan.item.target);
|
|
1961
2174
|
try {
|
|
1962
2175
|
if (deps.commitPromptDispatch && !deps.commitPromptDispatch(plan.item)) {
|
|
@@ -1990,6 +2203,7 @@ export function createTelegramQueueDispatchRuntime(deps) {
|
|
|
1990
2203
|
sendUserMessage: deps.sendUserMessage,
|
|
1991
2204
|
onPromptDispatchFailure: deps.onPromptDispatchFailure,
|
|
1992
2205
|
reconcileNextDispatchAnnouncementReplyOwnership: deps.reconcileNextDispatchAnnouncementReplyOwnership,
|
|
2206
|
+
steerDispatch: deps.steerDispatch,
|
|
1993
2207
|
isQueueItemTransportActive: deps.isQueueItemTransportActive,
|
|
1994
2208
|
hasPendingInboundQueueMutationForItem: deps.hasPendingInboundQueueMutationForItem,
|
|
1995
2209
|
isQueueItemAdmissionReady: deps.isQueueItemAdmissionReady,
|
|
@@ -2027,6 +2241,11 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
2027
2241
|
const retainedItems = [];
|
|
2028
2242
|
let droppedInactiveItemCount = 0;
|
|
2029
2243
|
for (const item of queuedItems) {
|
|
2244
|
+
if (item.kind === "prompt" && item.steerAttempted) {
|
|
2245
|
+
protectedInactiveItems.push(item);
|
|
2246
|
+
retainedItems.push(item);
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2030
2249
|
if (!deps.isQueueItemTransportActive ||
|
|
2031
2250
|
deps.isQueueItemTransportActive(item)) {
|
|
2032
2251
|
activeItems.push(item);
|
|
@@ -2116,19 +2335,34 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
2116
2335
|
return;
|
|
2117
2336
|
}
|
|
2118
2337
|
const dispatchBasisItems = deps.getQueuedItems();
|
|
2119
|
-
|
|
2338
|
+
let dispatchPlan = planNextTelegramQueueAction(dispatchableItems, canDispatch);
|
|
2339
|
+
if (canDispatch && nextItem?.kind === "prompt" && nextItem.queueLane === "steer" &&
|
|
2340
|
+
deps.steerDispatch && deps.steerDispatch.isContextCurrent?.(ctx) !== false &&
|
|
2341
|
+
deps.steerDispatch.canSubmit?.(ctx) !== false) {
|
|
2342
|
+
const packetPlan = planTelegramSteerPacket(deps.getQueuedItems(), {
|
|
2343
|
+
blocked: false,
|
|
2344
|
+
isItemReady: item => deps.isQueueItemTransportActive?.(item) !== false &&
|
|
2345
|
+
deps.hasPendingInboundQueueMutationForItem?.(item) !== true &&
|
|
2346
|
+
deps.isQueueItemAdmissionReady?.(item) !== false,
|
|
2347
|
+
canAppend: createTelegramSteerPacketBudget(true),
|
|
2348
|
+
});
|
|
2349
|
+
if (packetPlan.kind === "packet" && packetPlan.items[0] === nextItem) {
|
|
2350
|
+
dispatchPlan = { kind: "steer", item: nextItem, packet: packetPlan.items, remainingItems: dispatchableItems };
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2120
2353
|
const commitDispatchPlan = () => {
|
|
2121
2354
|
if (dispatchPlan.kind === "none")
|
|
2122
2355
|
return true;
|
|
2123
2356
|
const currentItems = deps.getQueuedItems();
|
|
2124
|
-
if (dispatchPlan.kind === "prompt") {
|
|
2357
|
+
if (dispatchPlan.kind === "prompt" || dispatchPlan.kind === "steer") {
|
|
2358
|
+
const inputs = dispatchPlan.kind === "steer" ? dispatchPlan.packet : [dispatchPlan.item];
|
|
2125
2359
|
const queueDrifted = currentItems.length !== dispatchBasisItems.length ||
|
|
2126
2360
|
currentItems.some((item, index) => item !== dispatchBasisItems[index]);
|
|
2127
2361
|
const dispatchEligibilityDrifted = !deps.canDispatch(ctx) ||
|
|
2128
|
-
deps.
|
|
2129
|
-
|
|
2130
|
-
deps.
|
|
2131
|
-
|
|
2362
|
+
(dispatchPlan.kind === "steer" && (deps.steerDispatch?.isContextCurrent?.(ctx) === false ||
|
|
2363
|
+
deps.steerDispatch?.canSubmit?.(ctx) === false)) ||
|
|
2364
|
+
inputs.some(item => deps.hasPendingInboundQueueMutationForItem?.(item) === true ||
|
|
2365
|
+
deps.isQueueItemAdmissionReady?.(item) === false || deps.isQueueItemTransportActive?.(item) === false);
|
|
2132
2366
|
if (queueDrifted || dispatchEligibilityDrifted) {
|
|
2133
2367
|
const selectedItemRetained = currentItems.includes(dispatchPlan.item);
|
|
2134
2368
|
const selectedTransportActive = deps.isQueueItemTransportActive?.(dispatchPlan.item) !== false;
|
|
@@ -2156,6 +2390,60 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
2156
2390
|
const executePlan = () => {
|
|
2157
2391
|
if (!commitDispatchPlan())
|
|
2158
2392
|
return;
|
|
2393
|
+
if (dispatchPlan.kind === "steer") {
|
|
2394
|
+
const { packet, item: first } = dispatchPlan;
|
|
2395
|
+
const content = buildTelegramSteerPacketContent(packet, true);
|
|
2396
|
+
const generation = deps.getDispatchGeneration?.();
|
|
2397
|
+
const current = () => {
|
|
2398
|
+
try {
|
|
2399
|
+
return (!deps.hasDispatchContext || deps.hasDispatchContext()) &&
|
|
2400
|
+
(generation === undefined || deps.isDispatchGenerationActive?.(generation) !== false) &&
|
|
2401
|
+
deps.steerDispatch?.isContextCurrent?.(ctx) !== false;
|
|
2402
|
+
}
|
|
2403
|
+
catch {
|
|
2404
|
+
return false;
|
|
2405
|
+
}
|
|
2406
|
+
};
|
|
2407
|
+
const refuse = (message) => {
|
|
2408
|
+
deps.recordRuntimeEvent?.("dispatch", new Error(message), { phase: "steer-idle-attempt" });
|
|
2409
|
+
if (current())
|
|
2410
|
+
deps.onPromptDispatchFailure(ctx, message);
|
|
2411
|
+
};
|
|
2412
|
+
controlDispatchPending = true;
|
|
2413
|
+
try {
|
|
2414
|
+
for (const item of packet)
|
|
2415
|
+
item.steerAttempted = true;
|
|
2416
|
+
if (deps.steerDispatch?.commitPacket(packet, ctx) !== true) {
|
|
2417
|
+
refuse("Steer packet receipt settlement is unconfirmed.");
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
const sourceChanged = packet.some(item => !deps.getQueuedItems().includes(item) ||
|
|
2421
|
+
deps.isQueueItemTransportActive?.(item) === false || deps.hasPendingInboundQueueMutationForItem?.(item) === true);
|
|
2422
|
+
const readyControl = deps.getQueuedItems().some(item => item.queueLane === "control" &&
|
|
2423
|
+
deps.isQueueItemTransportActive?.(item) !== false && deps.isQueueItemAdmissionReady?.(item) !== false);
|
|
2424
|
+
if (!current() || deps.steerDispatch?.canSubmit?.(ctx) === false || !deps.canDispatch(ctx) || sourceChanged || readyControl) {
|
|
2425
|
+
refuse("Steer packet lost idle readiness before submission.");
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
first.steerPacketTail = packet.slice(1);
|
|
2429
|
+
// Pin the original head, retaining every original until agent_start consumes the packet.
|
|
2430
|
+
deps.setQueuedItems([first, ...deps.getQueuedItems().filter(item => item !== first)]);
|
|
2431
|
+
deps.onPromptDispatchStart(ctx, first.chatId, first.target);
|
|
2432
|
+
if (!current() || deps.steerDispatch?.canSubmit?.(ctx) === false || packet.some(item => !deps.getQueuedItems().includes(item) ||
|
|
2433
|
+
deps.isQueueItemTransportActive?.(item) === false || deps.hasPendingInboundQueueMutationForItem?.(item) === true)) {
|
|
2434
|
+
refuse("Steer packet authority changed before submission.");
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
deps.sendUserMessage(content);
|
|
2438
|
+
}
|
|
2439
|
+
catch (error) {
|
|
2440
|
+
refuse(getTelegramQueueErrorMessage(error));
|
|
2441
|
+
}
|
|
2442
|
+
finally {
|
|
2443
|
+
controlDispatchPending = false;
|
|
2444
|
+
}
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2159
2447
|
executeTelegramQueueDispatchPlan(dispatchPlan, {
|
|
2160
2448
|
executeControlItem: (item) => {
|
|
2161
2449
|
controlDispatchPending = true;
|
|
@@ -2203,7 +2491,7 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
2203
2491
|
},
|
|
2204
2492
|
});
|
|
2205
2493
|
};
|
|
2206
|
-
if (dispatchPlan.kind === "prompt" && nextDispatchAnnouncementRequested) {
|
|
2494
|
+
if ((dispatchPlan.kind === "prompt" || dispatchPlan.kind === "steer") && nextDispatchAnnouncementRequested) {
|
|
2207
2495
|
nextDispatchAnnouncementRequested = false;
|
|
2208
2496
|
controlDispatchPending = true;
|
|
2209
2497
|
const announcementGeneration = nextDispatchAnnouncementGeneration;
|