@llblab/pi-telegram 0.51.0 → 0.51.2
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 +4 -3
- package/BACKLOG.md +3 -8
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/lib/bindings.d.ts +2 -0
- package/dist/lib/bindings.js +3 -4
- package/dist/lib/bus-api.d.ts +0 -1
- package/dist/lib/bus-api.js +0 -3
- package/dist/lib/bus-follower.d.ts +3 -1
- package/dist/lib/bus-follower.js +6 -1
- package/dist/lib/commands.d.ts +5 -0
- package/dist/lib/commands.js +9 -0
- package/dist/lib/extension.js +6 -3
- package/dist/lib/menu-model.d.ts +3 -3
- package/dist/lib/menu-model.js +17 -3
- package/dist/lib/menu.d.ts +5 -7
- package/dist/lib/menu.js +4 -5
- package/dist/lib/model.d.ts +12 -12
- package/dist/lib/model.js +49 -35
- package/dist/lib/queue.d.ts +8 -2
- package/dist/lib/queue.js +64 -19
- package/dist/lib/replies.d.ts +4 -0
- package/dist/lib/replies.js +14 -0
- package/dist/lib/routing.d.ts +1 -0
- package/dist/lib/routing.js +5 -1
- package/dist/lib/runtime.d.ts +3 -6
- package/dist/lib/runtime.js +4 -55
- package/dist/lib/telegram-api.js +36 -10
- package/dist/package.json +1 -1
- package/docs/architecture.md +9 -9
- package/docs/multi-instance-bus.md +3 -3
- package/docs/public-api.md +1 -1
- package/lib/bindings.ts +14 -4
- package/lib/bus-api.ts +0 -7
- package/lib/bus-follower.ts +13 -2
- package/lib/commands.ts +19 -0
- package/lib/extension.ts +10 -3
- package/lib/menu-model.ts +25 -4
- package/lib/menu.ts +14 -9
- package/lib/model.ts +77 -44
- package/lib/queue.ts +76 -24
- package/lib/replies.ts +18 -0
- package/lib/routing.ts +7 -1
- package/lib/runtime.ts +7 -58
- package/lib/telegram-api.ts +43 -17
- package/package.json +1 -1
package/dist/lib/model.js
CHANGED
|
@@ -261,12 +261,12 @@ export function createPendingModelSwitchStore() {
|
|
|
261
261
|
has: () => selection !== undefined,
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
-
export function
|
|
265
|
-
return !state.isIdle && state.
|
|
264
|
+
export function canRestartAgentRunForTelegramModelSwitch(state) {
|
|
265
|
+
return !state.isIdle && state.hasAbortHandler;
|
|
266
266
|
}
|
|
267
267
|
export function shouldTriggerPendingTelegramModelSwitchAbort(state) {
|
|
268
268
|
return (state.hasPendingModelSwitch &&
|
|
269
|
-
state.
|
|
269
|
+
state.hasContinuationTurn &&
|
|
270
270
|
state.hasAbortHandler &&
|
|
271
271
|
state.activeToolExecutions === 0);
|
|
272
272
|
}
|
|
@@ -297,7 +297,7 @@ export function buildTelegramModelSwitchContinuationText(telegramPrefix, model,
|
|
|
297
297
|
const thinkingSuffix = thinkingLevel
|
|
298
298
|
? ` Keep the selected thinking level (${thinkingLevel}) if it still applies.`
|
|
299
299
|
: "";
|
|
300
|
-
return `${telegramPrefix} Continue the interrupted previous
|
|
300
|
+
return `${telegramPrefix} Continue the interrupted previous request using the newly selected model (${modelLabel}). Resume from the last unfinished step instead of restarting from scratch unless necessary.${thinkingSuffix}`;
|
|
301
301
|
}
|
|
302
302
|
export function buildTelegramModelSwitchContinuationTurn(options) {
|
|
303
303
|
const modelLabel = `${options.selection.model.provider}/${options.selection.model.id}`;
|
|
@@ -318,7 +318,7 @@ export function buildTelegramModelSwitchContinuationTurn(options) {
|
|
|
318
318
|
text: buildTelegramModelSwitchContinuationText(options.telegramPrefix ?? TELEGRAM_PREFIX, options.selection.model, options.selection.thinkingLevel),
|
|
319
319
|
},
|
|
320
320
|
],
|
|
321
|
-
historyText: `Continue interrupted
|
|
321
|
+
historyText: `Continue interrupted request on ${modelLabel}`,
|
|
322
322
|
statusSummary: `↻ ${statusLabel || "continue"}`,
|
|
323
323
|
};
|
|
324
324
|
}
|
|
@@ -364,46 +364,60 @@ export function createTelegramModelSwitchControllerRuntime(deps) {
|
|
|
364
364
|
});
|
|
365
365
|
}
|
|
366
366
|
export function createTelegramModelSwitchController(deps) {
|
|
367
|
+
let pendingContinuationTurn;
|
|
368
|
+
const triggerPendingAbort = (ctx) => {
|
|
369
|
+
const turn = pendingContinuationTurn ?? deps.getActiveTurn();
|
|
370
|
+
if (!shouldTriggerPendingTelegramModelSwitchAbort({
|
|
371
|
+
hasPendingModelSwitch: !!deps.getPendingModelSwitch(),
|
|
372
|
+
hasContinuationTurn: !!turn,
|
|
373
|
+
hasAbortHandler: deps.hasAbortHandler(),
|
|
374
|
+
activeToolExecutions: deps.getActiveToolExecutions(),
|
|
375
|
+
})) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
const selection = deps.getPendingModelSwitch();
|
|
379
|
+
const abort = deps.getAbortHandler();
|
|
380
|
+
if (!selection || !turn || !abort)
|
|
381
|
+
return false;
|
|
382
|
+
pendingContinuationTurn = undefined;
|
|
383
|
+
deps.setPendingModelSwitch(undefined);
|
|
384
|
+
deps.queueContinuation(turn, selection, ctx);
|
|
385
|
+
abort();
|
|
386
|
+
return true;
|
|
387
|
+
};
|
|
367
388
|
return {
|
|
368
|
-
canOfferInFlightSwitch: (ctx) =>
|
|
389
|
+
canOfferInFlightSwitch: (ctx) => canRestartAgentRunForTelegramModelSwitch({
|
|
369
390
|
isIdle: deps.isIdle(ctx),
|
|
370
|
-
hasActiveTelegramTurn: !!deps.getActiveTurn(),
|
|
371
391
|
hasAbortHandler: deps.hasAbortHandler(),
|
|
372
392
|
}),
|
|
373
|
-
stagePendingSwitch: (selection, ctx) => {
|
|
393
|
+
stagePendingSwitch: (selection, ctx, continuationTurn) => {
|
|
394
|
+
pendingContinuationTurn = deps.getActiveTurn() ?? continuationTurn;
|
|
374
395
|
deps.setPendingModelSwitch(selection);
|
|
375
|
-
|
|
396
|
+
try {
|
|
397
|
+
deps.updateStatus(ctx);
|
|
398
|
+
}
|
|
399
|
+
finally {
|
|
400
|
+
triggerPendingAbort(ctx);
|
|
401
|
+
}
|
|
376
402
|
},
|
|
377
403
|
clearPendingSwitch: () => {
|
|
404
|
+
pendingContinuationTurn = undefined;
|
|
378
405
|
deps.setPendingModelSwitch(undefined);
|
|
379
406
|
},
|
|
380
407
|
queueContinuation: deps.queueContinuation,
|
|
381
|
-
triggerPendingAbort
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return false;
|
|
395
|
-
deps.setPendingModelSwitch(undefined);
|
|
396
|
-
deps.queueContinuation(turn, selection, ctx);
|
|
397
|
-
abort();
|
|
398
|
-
return true;
|
|
408
|
+
triggerPendingAbort,
|
|
409
|
+
restartInterruptedTurn: (selection, ctx, continuationTurn) => {
|
|
410
|
+
const restarted = restartTelegramModelSwitchContinuation({
|
|
411
|
+
activeTurn: deps.getActiveTurn() ?? continuationTurn,
|
|
412
|
+
abort: deps.getAbortHandler(),
|
|
413
|
+
selection,
|
|
414
|
+
queueContinuation: (turn, nextSelection) => {
|
|
415
|
+
deps.queueContinuation(turn, nextSelection, ctx);
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
if (restarted)
|
|
419
|
+
pendingContinuationTurn = undefined;
|
|
420
|
+
return restarted;
|
|
399
421
|
},
|
|
400
|
-
restartInterruptedTurn: (selection, ctx) => restartTelegramModelSwitchContinuation({
|
|
401
|
-
activeTurn: deps.getActiveTurn(),
|
|
402
|
-
abort: deps.getAbortHandler(),
|
|
403
|
-
selection,
|
|
404
|
-
queueContinuation: (turn, nextSelection) => {
|
|
405
|
-
deps.queueContinuation(turn, nextSelection, ctx);
|
|
406
|
-
},
|
|
407
|
-
}),
|
|
408
422
|
};
|
|
409
423
|
}
|
package/dist/lib/queue.d.ts
CHANGED
|
@@ -84,6 +84,8 @@ export interface PendingTelegramTurn extends TelegramQueueItemBase {
|
|
|
84
84
|
historyText: string;
|
|
85
85
|
priorityEmoji?: string;
|
|
86
86
|
reactionSuppressionEmoji?: string;
|
|
87
|
+
/** Emit the explicit aborted-turn notice when /next settles this active turn. */
|
|
88
|
+
announceNextAbortOnEnd?: boolean;
|
|
87
89
|
/** Turn should preferably be delivered as voice (mirror mode + user sent voice) */
|
|
88
90
|
voiceReplyPreferred?: boolean;
|
|
89
91
|
/** Turn must be delivered as voice (voice mode) */
|
|
@@ -165,6 +167,8 @@ export interface TelegramActiveTurnStore<TTurn extends PendingTelegramTurn = Pen
|
|
|
165
167
|
has: () => boolean;
|
|
166
168
|
set: (turn: TTurn) => void;
|
|
167
169
|
clear: () => void;
|
|
170
|
+
markNextAbortAnnouncement: () => boolean;
|
|
171
|
+
clearNextAbortAnnouncement: () => boolean;
|
|
168
172
|
getChatId: () => number | undefined;
|
|
169
173
|
getTarget: () => TelegramQueueTarget | undefined;
|
|
170
174
|
getReplyToMessageId: () => number | undefined;
|
|
@@ -349,7 +353,6 @@ export interface TelegramAgentStartHookRuntimeDeps<TTurn extends PendingTelegram
|
|
|
349
353
|
}
|
|
350
354
|
export type TelegramAgentStartHookEvent = unknown;
|
|
351
355
|
export interface TelegramToolExecutionRuntimeDeps {
|
|
352
|
-
hasActiveTurn: () => boolean;
|
|
353
356
|
getActiveToolExecutions: () => number;
|
|
354
357
|
setActiveToolExecutions: (count: number) => void;
|
|
355
358
|
}
|
|
@@ -368,7 +371,6 @@ export declare function buildTelegramAgentStartPlan<TContext = unknown>(options:
|
|
|
368
371
|
export declare function handleTelegramAgentStartRuntime<TTurn extends PendingTelegramTurn, TContext = unknown>(deps: TelegramAgentStartRuntimeDeps<TTurn, TContext>): void;
|
|
369
372
|
export declare function createTelegramAgentStartHook<TTurn extends PendingTelegramTurn, TContext = unknown>(deps: TelegramAgentStartHookRuntimeDeps<TTurn, TContext>): (_event: TelegramAgentStartHookEvent, ctx: TContext) => Promise<void>;
|
|
370
373
|
export declare function getNextTelegramToolExecutionCount(options: {
|
|
371
|
-
hasActiveTurn: boolean;
|
|
372
374
|
currentCount: number;
|
|
373
375
|
event: "start" | "end";
|
|
374
376
|
}): number;
|
|
@@ -391,6 +393,7 @@ export interface TelegramAgentEndPlan {
|
|
|
391
393
|
kind: "no-turn" | "aborted" | "error" | "text" | "attachments-only" | "empty";
|
|
392
394
|
shouldClearPreview: boolean;
|
|
393
395
|
shouldDispatchNext: boolean;
|
|
396
|
+
shouldSendAbortMessage: boolean;
|
|
394
397
|
shouldSendErrorMessage: boolean;
|
|
395
398
|
shouldSendAttachmentNotice: boolean;
|
|
396
399
|
}
|
|
@@ -517,6 +520,7 @@ export declare function buildTelegramAgentEndPlan(options: {
|
|
|
517
520
|
hasFinalText: boolean;
|
|
518
521
|
hasQueuedAttachments: boolean;
|
|
519
522
|
foldQueuedPromptsIntoHistory: boolean;
|
|
523
|
+
announceNextAbortOnEnd?: boolean;
|
|
520
524
|
}): TelegramAgentEndPlan;
|
|
521
525
|
export declare function createTelegramAgentEndHook<TTurn extends PendingTelegramTurn, TContext, TMessage, TReplyMarkup = unknown>(deps: TelegramAgentEndHookRuntimeDeps<TTurn, TContext, TMessage, TReplyMarkup>): (event: TelegramAgentEndHookEvent<TMessage>, ctx: TContext, assistantOverride?: TelegramAgentEndAssistantResult) => Promise<void>;
|
|
522
526
|
export declare function handleTelegramAgentEndRuntime<TTurn extends PendingTelegramTurn, TReplyMarkup = unknown>(deps: TelegramAgentEndRuntimeDeps<TTurn, TReplyMarkup>): Promise<void>;
|
|
@@ -759,6 +763,7 @@ export interface TelegramQueueDispatchControllerDeps<TContext = unknown> extends
|
|
|
759
763
|
}>["item"], ctx: TContext) => boolean;
|
|
760
764
|
sendUserMessage: TelegramDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
761
765
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
766
|
+
reconcileNextDispatchAnnouncementReplyOwnership?: (item: PendingTelegramTurn) => void;
|
|
762
767
|
isQueueItemTransportActive?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
763
768
|
hasPendingInboundQueueMutationForItem?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
764
769
|
isQueueItemAdmissionReady?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
@@ -768,6 +773,7 @@ export interface TelegramQueueDispatchControllerDeps<TContext = unknown> extends
|
|
|
768
773
|
export interface TelegramQueueDispatchController<TContext = unknown> {
|
|
769
774
|
dispatchNext: (ctx: TContext) => void;
|
|
770
775
|
requestNextDispatchAnnouncement: () => void;
|
|
776
|
+
cancelNextDispatchAnnouncement: () => void;
|
|
771
777
|
}
|
|
772
778
|
export declare function executeTelegramQueueDispatchPlan<TContext = unknown>(plan: TelegramQueueDispatchAction<TContext>, deps: TelegramDispatchRuntimeDeps<TContext>): void;
|
|
773
779
|
export type TelegramQueueDispatchRuntimeDeps<TContext = unknown> = Omit<TelegramQueueDispatchControllerDeps<TContext>, "canDispatch"> & TelegramDispatchReadinessDeps<TContext>;
|
package/dist/lib/queue.js
CHANGED
|
@@ -168,6 +168,18 @@ export function createTelegramActiveTurnStore() {
|
|
|
168
168
|
clear: () => {
|
|
169
169
|
activeTurn = undefined;
|
|
170
170
|
},
|
|
171
|
+
markNextAbortAnnouncement: () => {
|
|
172
|
+
if (!activeTurn)
|
|
173
|
+
return false;
|
|
174
|
+
activeTurn.announceNextAbortOnEnd = true;
|
|
175
|
+
return true;
|
|
176
|
+
},
|
|
177
|
+
clearNextAbortAnnouncement: () => {
|
|
178
|
+
if (!activeTurn?.announceNextAbortOnEnd)
|
|
179
|
+
return false;
|
|
180
|
+
delete activeTurn.announceNextAbortOnEnd;
|
|
181
|
+
return true;
|
|
182
|
+
},
|
|
171
183
|
getChatId: () => activeTurn?.chatId,
|
|
172
184
|
getTarget: () => activeTurn?.target ? { ...activeTurn.target } : undefined,
|
|
173
185
|
getReplyToMessageId: () => activeTurn?.replyToMessageId,
|
|
@@ -729,8 +741,6 @@ export function createTelegramAgentStartHook(deps) {
|
|
|
729
741
|
};
|
|
730
742
|
}
|
|
731
743
|
export function getNextTelegramToolExecutionCount(options) {
|
|
732
|
-
if (!options.hasActiveTurn)
|
|
733
|
-
return options.currentCount;
|
|
734
744
|
if (options.event === "start") {
|
|
735
745
|
return options.currentCount + 1;
|
|
736
746
|
}
|
|
@@ -738,20 +748,16 @@ export function getNextTelegramToolExecutionCount(options) {
|
|
|
738
748
|
}
|
|
739
749
|
export function handleTelegramToolExecutionStartRuntime(deps) {
|
|
740
750
|
deps.setActiveToolExecutions(getNextTelegramToolExecutionCount({
|
|
741
|
-
hasActiveTurn: deps.hasActiveTurn(),
|
|
742
751
|
currentCount: deps.getActiveToolExecutions(),
|
|
743
752
|
event: "start",
|
|
744
753
|
}));
|
|
745
754
|
}
|
|
746
755
|
export function handleTelegramToolExecutionEndRuntime(deps) {
|
|
747
|
-
const hasActiveTurn = deps.hasActiveTurn();
|
|
748
756
|
deps.setActiveToolExecutions(getNextTelegramToolExecutionCount({
|
|
749
|
-
hasActiveTurn,
|
|
750
757
|
currentCount: deps.getActiveToolExecutions(),
|
|
751
758
|
event: "end",
|
|
752
759
|
}));
|
|
753
|
-
|
|
754
|
-
deps.triggerPendingModelSwitchAbort();
|
|
760
|
+
deps.triggerPendingModelSwitchAbort();
|
|
755
761
|
}
|
|
756
762
|
export function createTelegramAgentLifecycleHooks(deps) {
|
|
757
763
|
const onAgentStart = createTelegramAgentStartHook(deps);
|
|
@@ -794,7 +800,6 @@ export function createTelegramToolExecutionHooks(deps) {
|
|
|
794
800
|
},
|
|
795
801
|
onToolExecutionEnd: (_event, ctx) => {
|
|
796
802
|
handleTelegramToolExecutionEndRuntime({
|
|
797
|
-
hasActiveTurn: deps.hasActiveTurn,
|
|
798
803
|
getActiveToolExecutions: deps.getActiveToolExecutions,
|
|
799
804
|
setActiveToolExecutions: deps.setActiveToolExecutions,
|
|
800
805
|
triggerPendingModelSwitchAbort: () => {
|
|
@@ -815,6 +820,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
815
820
|
kind: "no-turn",
|
|
816
821
|
shouldClearPreview: false,
|
|
817
822
|
shouldDispatchNext,
|
|
823
|
+
shouldSendAbortMessage: false,
|
|
818
824
|
shouldSendErrorMessage: false,
|
|
819
825
|
shouldSendAttachmentNotice: false,
|
|
820
826
|
};
|
|
@@ -824,6 +830,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
824
830
|
kind: "aborted",
|
|
825
831
|
shouldClearPreview: true,
|
|
826
832
|
shouldDispatchNext,
|
|
833
|
+
shouldSendAbortMessage: options.announceNextAbortOnEnd === true,
|
|
827
834
|
shouldSendErrorMessage: false,
|
|
828
835
|
shouldSendAttachmentNotice: false,
|
|
829
836
|
};
|
|
@@ -833,6 +840,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
833
840
|
kind: "error",
|
|
834
841
|
shouldClearPreview: true,
|
|
835
842
|
shouldDispatchNext,
|
|
843
|
+
shouldSendAbortMessage: false,
|
|
836
844
|
shouldSendErrorMessage: true,
|
|
837
845
|
shouldSendAttachmentNotice: false,
|
|
838
846
|
};
|
|
@@ -842,6 +850,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
842
850
|
kind: "text",
|
|
843
851
|
shouldClearPreview: false,
|
|
844
852
|
shouldDispatchNext,
|
|
853
|
+
shouldSendAbortMessage: false,
|
|
845
854
|
shouldSendErrorMessage: false,
|
|
846
855
|
shouldSendAttachmentNotice: false,
|
|
847
856
|
};
|
|
@@ -851,6 +860,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
851
860
|
kind: "attachments-only",
|
|
852
861
|
shouldClearPreview: true,
|
|
853
862
|
shouldDispatchNext,
|
|
863
|
+
shouldSendAbortMessage: false,
|
|
854
864
|
shouldSendErrorMessage: false,
|
|
855
865
|
shouldSendAttachmentNotice: true,
|
|
856
866
|
};
|
|
@@ -859,6 +869,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
859
869
|
kind: "empty",
|
|
860
870
|
shouldClearPreview: true,
|
|
861
871
|
shouldDispatchNext,
|
|
872
|
+
shouldSendAbortMessage: false,
|
|
862
873
|
shouldSendErrorMessage: false,
|
|
863
874
|
shouldSendAttachmentNotice: false,
|
|
864
875
|
};
|
|
@@ -991,6 +1002,7 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
991
1002
|
hasFinalText: !!finalText || hasOutboundArtifacts,
|
|
992
1003
|
hasQueuedAttachments: (turn?.queuedAttachments.length ?? 0) > 0,
|
|
993
1004
|
foldQueuedPromptsIntoHistory: deps.foldQueuedPromptsIntoHistory,
|
|
1005
|
+
announceNextAbortOnEnd: turn?.announceNextAbortOnEnd === true,
|
|
994
1006
|
});
|
|
995
1007
|
if (!turn) {
|
|
996
1008
|
if (endPlan.shouldDispatchNext)
|
|
@@ -1116,17 +1128,26 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
1116
1128
|
if (!isDeliveryActive())
|
|
1117
1129
|
return;
|
|
1118
1130
|
}
|
|
1119
|
-
if (endPlan.shouldSendErrorMessage) {
|
|
1131
|
+
if (endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage) {
|
|
1120
1132
|
const errorMessage = assistant.errorMessage ||
|
|
1121
1133
|
"Telegram bridge: Pi failed while processing the request.";
|
|
1122
|
-
const isOperationAborted =
|
|
1123
|
-
"This operation was aborted";
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1134
|
+
const isOperationAborted = endPlan.shouldSendAbortMessage ||
|
|
1135
|
+
errorMessage.trim().replace(/\.$/, "") === "This operation was aborted";
|
|
1136
|
+
try {
|
|
1137
|
+
await deps.sendTextReply(turn.chatId, turn.replyToMessageId, isOperationAborted
|
|
1138
|
+
? "<b>⏹️ This operation was aborted.</b>"
|
|
1139
|
+
: errorMessage, {
|
|
1140
|
+
target: turn.target,
|
|
1141
|
+
...(isOperationAborted ? { parseMode: "HTML" } : {}),
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
catch (error) {
|
|
1145
|
+
if (!isOperationAborted)
|
|
1146
|
+
throw error;
|
|
1147
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
1148
|
+
phase: "next-abort-announcement",
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1130
1151
|
if (!isDeliveryActive())
|
|
1131
1152
|
return;
|
|
1132
1153
|
if (endPlan.shouldDispatchNext)
|
|
@@ -1267,7 +1288,7 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
1267
1288
|
deps.dispatchNextQueuedTelegramTurn();
|
|
1268
1289
|
};
|
|
1269
1290
|
if (deps.scheduleActiveTurnDelivery &&
|
|
1270
|
-
(endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)) {
|
|
1291
|
+
(endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)) {
|
|
1271
1292
|
deps.scheduleActiveTurnDelivery(deliverActiveTurn);
|
|
1272
1293
|
return;
|
|
1273
1294
|
}
|
|
@@ -1765,6 +1786,7 @@ export function createTelegramQueueDispatchRuntime(deps) {
|
|
|
1765
1786
|
commitPromptDispatch: deps.commitPromptDispatch,
|
|
1766
1787
|
sendUserMessage: deps.sendUserMessage,
|
|
1767
1788
|
onPromptDispatchFailure: deps.onPromptDispatchFailure,
|
|
1789
|
+
reconcileNextDispatchAnnouncementReplyOwnership: deps.reconcileNextDispatchAnnouncementReplyOwnership,
|
|
1768
1790
|
isQueueItemTransportActive: deps.isQueueItemTransportActive,
|
|
1769
1791
|
hasPendingInboundQueueMutationForItem: deps.hasPendingInboundQueueMutationForItem,
|
|
1770
1792
|
isQueueItemAdmissionReady: deps.isQueueItemAdmissionReady,
|
|
@@ -1776,12 +1798,19 @@ export function createTelegramQueueDispatchRuntime(deps) {
|
|
|
1776
1798
|
export function createTelegramQueueDispatchController(deps) {
|
|
1777
1799
|
let controlDispatchPending = false;
|
|
1778
1800
|
let nextDispatchAnnouncementRequested = false;
|
|
1801
|
+
let nextDispatchAnnouncementGeneration = 0;
|
|
1779
1802
|
let nextDispatchAnnouncementAnchor;
|
|
1780
1803
|
const controller = {
|
|
1781
1804
|
requestNextDispatchAnnouncement: () => {
|
|
1805
|
+
nextDispatchAnnouncementGeneration += 1;
|
|
1782
1806
|
nextDispatchAnnouncementRequested = true;
|
|
1783
1807
|
nextDispatchAnnouncementAnchor = undefined;
|
|
1784
1808
|
},
|
|
1809
|
+
cancelNextDispatchAnnouncement: () => {
|
|
1810
|
+
nextDispatchAnnouncementGeneration += 1;
|
|
1811
|
+
nextDispatchAnnouncementRequested = false;
|
|
1812
|
+
nextDispatchAnnouncementAnchor = undefined;
|
|
1813
|
+
},
|
|
1785
1814
|
dispatchNext: (ctx) => {
|
|
1786
1815
|
if (deps.hasDispatchContext && !deps.hasDispatchContext())
|
|
1787
1816
|
return;
|
|
@@ -1972,11 +2001,22 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
1972
2001
|
if (dispatchPlan.kind === "prompt" && nextDispatchAnnouncementRequested) {
|
|
1973
2002
|
nextDispatchAnnouncementRequested = false;
|
|
1974
2003
|
controlDispatchPending = true;
|
|
2004
|
+
const announcementGeneration = nextDispatchAnnouncementGeneration;
|
|
1975
2005
|
const dispatchGeneration = deps.getDispatchGeneration?.();
|
|
1976
2006
|
deps.updateStatus(ctx);
|
|
1977
2007
|
void deps.sendTextReply(dispatchPlan.item.chatId, dispatchPlan.item.replyToMessageId, "<b>⏩ Dispatching next queued turn.</b>", { target: dispatchPlan.item.target }).catch((error) => {
|
|
1978
|
-
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2008
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2009
|
+
phase: "next-announcement",
|
|
2010
|
+
});
|
|
1979
2011
|
}).finally(() => {
|
|
2012
|
+
try {
|
|
2013
|
+
deps.reconcileNextDispatchAnnouncementReplyOwnership?.(dispatchPlan.item);
|
|
2014
|
+
}
|
|
2015
|
+
catch (error) {
|
|
2016
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2017
|
+
phase: "next-announcement-reply-ownership",
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
1980
2020
|
controlDispatchPending = false;
|
|
1981
2021
|
if (deps.hasDispatchContext && !deps.hasDispatchContext())
|
|
1982
2022
|
return;
|
|
@@ -1984,6 +2024,11 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
1984
2024
|
deps.isDispatchGenerationActive &&
|
|
1985
2025
|
!deps.isDispatchGenerationActive(dispatchGeneration))
|
|
1986
2026
|
return;
|
|
2027
|
+
if (announcementGeneration !== nextDispatchAnnouncementGeneration) {
|
|
2028
|
+
if (nextDispatchAnnouncementRequested)
|
|
2029
|
+
controller.dispatchNext(ctx);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
1987
2032
|
executePlan();
|
|
1988
2033
|
});
|
|
1989
2034
|
return;
|
package/dist/lib/replies.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface ReplyDedupRuntime {
|
|
|
23
23
|
}
|
|
24
24
|
export declare function createReplyDedupRuntime(): ReplyDedupRuntime;
|
|
25
25
|
export declare function resetTransportReplyDedup(): void;
|
|
26
|
+
/** Keeps a successfully published transition notice as the first reply of the
|
|
27
|
+
* next agent turn. The following agent-start reset consumes this one-shot
|
|
28
|
+
* preservation, so later messages in that turn do not repeat the reply header. */
|
|
29
|
+
export declare function preserveTransportReplyDedupOnNextReset(chatId: number, messageId: number, target?: TelegramTarget): void;
|
|
26
30
|
export declare function buildTelegramReplyParameters(chatId: number, messageId: number | undefined, target?: TelegramTarget): TelegramReplyParameters | undefined;
|
|
27
31
|
export declare function withTelegramReplyParameters<T>(chatId: number, messageId: number | undefined, target: TelegramTarget | undefined, send: (parameters: TelegramReplyParameters | undefined) => Promise<T>): Promise<T>;
|
|
28
32
|
export declare function isAssistantAgentMessage(message: unknown): boolean;
|
package/dist/lib/replies.js
CHANGED
|
@@ -31,6 +31,7 @@ export function createReplyDedupRuntime() {
|
|
|
31
31
|
}
|
|
32
32
|
// --- Transport-level dedup ---
|
|
33
33
|
const lastRepliedToMessageIdByTarget = new Map();
|
|
34
|
+
const replyDedupPreservedOnNextReset = new Map();
|
|
34
35
|
let replyDedupGeneration = 0;
|
|
35
36
|
function getReplyDedupTargetKey(chatId, target) {
|
|
36
37
|
const threadId = target?.threadId;
|
|
@@ -41,6 +42,19 @@ function getReplyDedupTargetKey(chatId, target) {
|
|
|
41
42
|
export function resetTransportReplyDedup() {
|
|
42
43
|
replyDedupGeneration += 1;
|
|
43
44
|
lastRepliedToMessageIdByTarget.clear();
|
|
45
|
+
for (const [key, messageId] of replyDedupPreservedOnNextReset) {
|
|
46
|
+
lastRepliedToMessageIdByTarget.set(key, messageId);
|
|
47
|
+
}
|
|
48
|
+
replyDedupPreservedOnNextReset.clear();
|
|
49
|
+
}
|
|
50
|
+
/** Keeps a successfully published transition notice as the first reply of the
|
|
51
|
+
* next agent turn. The following agent-start reset consumes this one-shot
|
|
52
|
+
* preservation, so later messages in that turn do not repeat the reply header. */
|
|
53
|
+
export function preserveTransportReplyDedupOnNextReset(chatId, messageId, target) {
|
|
54
|
+
const key = getReplyDedupTargetKey(chatId, target);
|
|
55
|
+
if (lastRepliedToMessageIdByTarget.get(key) !== messageId)
|
|
56
|
+
return;
|
|
57
|
+
replyDedupPreservedOnNextReset.set(key, messageId);
|
|
44
58
|
}
|
|
45
59
|
export function buildTelegramReplyParameters(chatId, messageId, target) {
|
|
46
60
|
if (messageId === undefined || messageId <= 0)
|
package/dist/lib/routing.d.ts
CHANGED
|
@@ -122,6 +122,7 @@ export interface TelegramInboundRouteRuntimeDeps<TMessage extends TelegramRouted
|
|
|
122
122
|
isContextActive?: (ctx: TContext) => boolean;
|
|
123
123
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
124
124
|
requestNextDispatchAnnouncement?: () => void;
|
|
125
|
+
cancelNextDispatchAnnouncement?: () => void;
|
|
125
126
|
requestDeferredDispatchNextQueuedTelegramTurn?: (dispatch: (ctx: TContext) => void) => void;
|
|
126
127
|
hasDeferredDispatchContext?: () => boolean;
|
|
127
128
|
startTypingLoop?: (ctx: TContext, chatId?: number, options?: {
|
package/dist/lib/routing.js
CHANGED
|
@@ -535,7 +535,6 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
535
535
|
updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
|
|
536
536
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
537
537
|
isIdle: deps.isIdle,
|
|
538
|
-
hasActiveTelegramTurn: deps.activeTurnRuntime.has,
|
|
539
538
|
hasAbortHandler: deps.bridgeRuntime.abort.hasHandler,
|
|
540
539
|
getActiveToolExecutions: deps.bridgeRuntime.lifecycle.getActiveToolExecutions,
|
|
541
540
|
persistScopedModelPatterns: deps.persistScopedModelPatterns,
|
|
@@ -1418,6 +1417,10 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
1418
1417
|
isContextActive: deps.isContextActive,
|
|
1419
1418
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
1420
1419
|
requestNextDispatchAnnouncement: deps.requestNextDispatchAnnouncement,
|
|
1420
|
+
cancelNextTransitionAnnouncements: () => {
|
|
1421
|
+
deps.activeTurnRuntime.clearNextAbortAnnouncement();
|
|
1422
|
+
deps.cancelNextDispatchAnnouncement?.();
|
|
1423
|
+
},
|
|
1421
1424
|
requestDeferredDispatchNextQueuedTelegramTurn: deps.requestDeferredDispatchNextQueuedTelegramTurn,
|
|
1422
1425
|
startTypingLoop: deps.startTypingLoop,
|
|
1423
1426
|
stopTypingLoop: deps.stopTypingLoop,
|
|
@@ -1476,6 +1479,7 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
1476
1479
|
},
|
|
1477
1480
|
getPromptTemplateCommands,
|
|
1478
1481
|
sendTextReply: deps.sendTextReply,
|
|
1482
|
+
markActiveTurnNextAbortAnnouncement: deps.activeTurnRuntime.markNextAbortAnnouncement,
|
|
1479
1483
|
getActiveTurnReply: () => {
|
|
1480
1484
|
const activeTurn = deps.activeTurnRuntime.get();
|
|
1481
1485
|
if (!activeTurn)
|
package/dist/lib/runtime.d.ts
CHANGED
|
@@ -98,7 +98,6 @@ export interface TelegramTypingLoopDeps {
|
|
|
98
98
|
sendTypingAction: (chatId: number, options?: {
|
|
99
99
|
message_thread_id?: number;
|
|
100
100
|
}) => Promise<unknown>;
|
|
101
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
102
101
|
shouldContinue?: () => boolean;
|
|
103
102
|
onStopped?: () => void;
|
|
104
103
|
}
|
|
@@ -111,7 +110,6 @@ export interface TelegramTypingLoopStarterDeps<TContext> extends TelegramRuntime
|
|
|
111
110
|
sendTypingAction: (chatId: number, options?: {
|
|
112
111
|
message_thread_id?: number;
|
|
113
112
|
}) => Promise<unknown>;
|
|
114
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
115
113
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
116
114
|
isContextActive?: (ctx: TContext) => boolean;
|
|
117
115
|
isTransportAvailable?: () => boolean;
|
|
@@ -120,7 +118,7 @@ export interface TelegramTypingLoopStarterDeps<TContext> extends TelegramRuntime
|
|
|
120
118
|
}
|
|
121
119
|
export declare function createTelegramTypingLoopStarter<TContext>(deps: TelegramTypingLoopStarterDeps<TContext>): (ctx: TContext, chatId?: number, options?: {
|
|
122
120
|
target?: TelegramTypingLoopTarget;
|
|
123
|
-
}) =>
|
|
121
|
+
}) => boolean;
|
|
124
122
|
export declare function startTelegramTypingLoop(state: TelegramBridgeRuntimeState, deps: TelegramTypingLoopDeps): boolean;
|
|
125
123
|
export declare function stopTelegramTypingLoop(state: TelegramBridgeRuntimeState): boolean;
|
|
126
124
|
export declare function waitForTelegramTypingLoopIdle(state: TelegramBridgeRuntimeState, timeoutMs?: number): Promise<void>;
|
|
@@ -141,7 +139,7 @@ export interface TelegramPromptDispatchLifecycleDeps<TContext> extends TelegramR
|
|
|
141
139
|
typing: Pick<TelegramRuntimeTypingPort, "stop">;
|
|
142
140
|
startTypingLoop: (ctx: TContext, chatId?: number, options?: {
|
|
143
141
|
target?: TelegramTypingLoopTarget;
|
|
144
|
-
}) => void;
|
|
142
|
+
}) => boolean | void;
|
|
145
143
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
146
144
|
}
|
|
147
145
|
export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRuntimeEventRecorderPort {
|
|
@@ -151,7 +149,6 @@ export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRun
|
|
|
151
149
|
sendTypingAction: (chatId: number, options?: {
|
|
152
150
|
message_thread_id?: number;
|
|
153
151
|
}) => Promise<unknown>;
|
|
154
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
155
152
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
156
153
|
isContextActive?: (ctx: TContext) => boolean;
|
|
157
154
|
isTransportAvailable?: () => boolean;
|
|
@@ -161,7 +158,7 @@ export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRun
|
|
|
161
158
|
export interface TelegramPromptDispatchRuntime<TContext> {
|
|
162
159
|
startTypingLoop: (ctx: TContext, chatId?: number, options?: {
|
|
163
160
|
target?: TelegramTypingLoopTarget;
|
|
164
|
-
}) => void;
|
|
161
|
+
}) => boolean | void;
|
|
165
162
|
onPromptDispatchStart: (ctx: TContext, chatId?: number) => void;
|
|
166
163
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
167
164
|
}
|
package/dist/lib/runtime.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zones: pi agent runtime state, telegram session, shared coordination
|
|
4
4
|
* Owns small session-local runtime primitives that are shared by orchestration but are not specific to queueing, rendering, polling, or Telegram transport
|
|
5
5
|
*/
|
|
6
|
-
const TELEGRAM_TYPING_ACTION_INTERVAL_MS =
|
|
6
|
+
const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 3_000;
|
|
7
7
|
const TELEGRAM_TYPING_IDLE_DRAIN_MAX_MS = 250;
|
|
8
8
|
export function createTelegramBridgeRuntimeState() {
|
|
9
9
|
return {
|
|
@@ -177,9 +177,9 @@ export function createTelegramTypingLoopStarter(deps) {
|
|
|
177
177
|
Object.is(deps.getTransportAuthority(), transportAuthority)
|
|
178
178
|
: deps.isTransportAvailable?.() !== false;
|
|
179
179
|
if (!hasTransport())
|
|
180
|
-
return;
|
|
180
|
+
return false;
|
|
181
181
|
let active = true;
|
|
182
|
-
deps.typing.start({
|
|
182
|
+
return deps.typing.start({
|
|
183
183
|
chatId: chatId ?? deps.getDefaultChatId(),
|
|
184
184
|
target: options?.target,
|
|
185
185
|
intervalMs: deps.intervalMs ?? TELEGRAM_TYPING_ACTION_INTERVAL_MS,
|
|
@@ -202,13 +202,6 @@ export function createTelegramTypingLoopStarter(deps) {
|
|
|
202
202
|
deps.typing.stop();
|
|
203
203
|
return;
|
|
204
204
|
}
|
|
205
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
-
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
207
|
-
error: message,
|
|
208
|
-
category: "typing",
|
|
209
|
-
phase: "status-update",
|
|
210
|
-
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
211
|
-
});
|
|
212
205
|
try {
|
|
213
206
|
deps.recordRuntimeEvent?.("typing", error, {
|
|
214
207
|
chatId: targetChatId,
|
|
@@ -219,45 +212,6 @@ export function createTelegramTypingLoopStarter(deps) {
|
|
|
219
212
|
}
|
|
220
213
|
}
|
|
221
214
|
},
|
|
222
|
-
sendAggregateTypingAction: deps.sendAggregateTypingAction
|
|
223
|
-
? async (targetChatId) => {
|
|
224
|
-
if (!active)
|
|
225
|
-
return;
|
|
226
|
-
if (!hasTransport()) {
|
|
227
|
-
deps.typing.stop();
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
await deps.sendAggregateTypingAction?.(targetChatId);
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
if (deps.isContextActive?.(ctx) === false)
|
|
235
|
-
return;
|
|
236
|
-
if (!active)
|
|
237
|
-
return;
|
|
238
|
-
if (!hasTransport()) {
|
|
239
|
-
deps.typing.stop();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
-
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
244
|
-
error: message,
|
|
245
|
-
category: "typing",
|
|
246
|
-
phase: "status-update",
|
|
247
|
-
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
248
|
-
});
|
|
249
|
-
try {
|
|
250
|
-
deps.recordRuntimeEvent?.("typing", error, {
|
|
251
|
-
chatId: targetChatId,
|
|
252
|
-
aggregate: true,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
// Typing diagnostics cannot escape the in-flight action owner.
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
: undefined,
|
|
261
215
|
shouldContinue: hasTransport,
|
|
262
216
|
onStopped: () => {
|
|
263
217
|
active = false;
|
|
@@ -296,12 +250,7 @@ export function startTelegramTypingLoop(state, deps) {
|
|
|
296
250
|
const targetChatId = activeDeps.chatId;
|
|
297
251
|
const threadParams = getTelegramTypingLoopThreadParams(activeDeps.target);
|
|
298
252
|
const typing = Promise.resolve()
|
|
299
|
-
.then(
|
|
300
|
-
await activeDeps.sendTypingAction(targetChatId, threadParams);
|
|
301
|
-
if (threadParams?.message_thread_id !== undefined) {
|
|
302
|
-
await activeDeps.sendAggregateTypingAction?.(targetChatId);
|
|
303
|
-
}
|
|
304
|
-
})
|
|
253
|
+
.then(() => activeDeps.sendTypingAction(targetChatId, threadParams))
|
|
305
254
|
.then(() => undefined)
|
|
306
255
|
.catch(() => undefined);
|
|
307
256
|
state.typingInFlight = typing;
|