@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/lib/model.ts
CHANGED
|
@@ -374,7 +374,6 @@ export interface PendingModelSwitchStore<TSelection> {
|
|
|
374
374
|
|
|
375
375
|
export interface TelegramInFlightModelSwitchState {
|
|
376
376
|
isIdle: boolean;
|
|
377
|
-
hasActiveTelegramTurn: boolean;
|
|
378
377
|
hasAbortHandler: boolean;
|
|
379
378
|
}
|
|
380
379
|
|
|
@@ -394,21 +393,21 @@ export function createPendingModelSwitchStore<
|
|
|
394
393
|
};
|
|
395
394
|
}
|
|
396
395
|
|
|
397
|
-
export function
|
|
396
|
+
export function canRestartAgentRunForTelegramModelSwitch(
|
|
398
397
|
state: TelegramInFlightModelSwitchState,
|
|
399
398
|
): boolean {
|
|
400
|
-
return !state.isIdle && state.
|
|
399
|
+
return !state.isIdle && state.hasAbortHandler;
|
|
401
400
|
}
|
|
402
401
|
|
|
403
402
|
export function shouldTriggerPendingTelegramModelSwitchAbort(state: {
|
|
404
403
|
hasPendingModelSwitch: boolean;
|
|
405
|
-
|
|
404
|
+
hasContinuationTurn: boolean;
|
|
406
405
|
hasAbortHandler: boolean;
|
|
407
406
|
activeToolExecutions: number;
|
|
408
407
|
}): boolean {
|
|
409
408
|
return (
|
|
410
409
|
state.hasPendingModelSwitch &&
|
|
411
|
-
state.
|
|
410
|
+
state.hasContinuationTurn &&
|
|
412
411
|
state.hasAbortHandler &&
|
|
413
412
|
state.activeToolExecutions === 0
|
|
414
413
|
);
|
|
@@ -458,13 +457,18 @@ export function buildTelegramModelSwitchContinuationText<
|
|
|
458
457
|
const thinkingSuffix = thinkingLevel
|
|
459
458
|
? ` Keep the selected thinking level (${thinkingLevel}) if it still applies.`
|
|
460
459
|
: "";
|
|
461
|
-
return `${telegramPrefix} Continue the interrupted previous
|
|
460
|
+
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}`;
|
|
462
461
|
}
|
|
463
462
|
|
|
463
|
+
export type TelegramModelSwitchContinuationSource = Pick<
|
|
464
|
+
PendingTelegramTurn,
|
|
465
|
+
"chatId" | "replyToMessageId" | "target"
|
|
466
|
+
>;
|
|
467
|
+
|
|
464
468
|
export function buildTelegramModelSwitchContinuationTurn<
|
|
465
469
|
TModel extends MenuModel,
|
|
466
470
|
>(options: {
|
|
467
|
-
turn:
|
|
471
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
468
472
|
selection: ScopedTelegramModel<TModel>;
|
|
469
473
|
telegramPrefix?: string;
|
|
470
474
|
queueOrder: number;
|
|
@@ -494,7 +498,7 @@ export function buildTelegramModelSwitchContinuationTurn<
|
|
|
494
498
|
),
|
|
495
499
|
},
|
|
496
500
|
],
|
|
497
|
-
historyText: `Continue interrupted
|
|
501
|
+
historyText: `Continue interrupted request on ${modelLabel}`,
|
|
498
502
|
statusSummary: `↻ ${statusLabel || "continue"}`,
|
|
499
503
|
};
|
|
500
504
|
}
|
|
@@ -506,7 +510,7 @@ export function createTelegramModelSwitchContinuationTurnBuilder<
|
|
|
506
510
|
allocateItemOrder: () => number;
|
|
507
511
|
allocateControlOrder: () => number;
|
|
508
512
|
}): (options: {
|
|
509
|
-
turn:
|
|
513
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
510
514
|
selection: ScopedTelegramModel<TModel>;
|
|
511
515
|
}) => PendingTelegramTurn {
|
|
512
516
|
return (options) =>
|
|
@@ -523,11 +527,15 @@ export function createTelegramModelSwitchContinuationQueue<
|
|
|
523
527
|
TSelection extends ScopedTelegramModel,
|
|
524
528
|
>(deps: {
|
|
525
529
|
createContinuationTurn: (options: {
|
|
526
|
-
turn:
|
|
530
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
527
531
|
selection: TSelection;
|
|
528
532
|
}) => PendingTelegramTurn;
|
|
529
533
|
appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
|
|
530
|
-
}): (
|
|
534
|
+
}): (
|
|
535
|
+
turn: TelegramModelSwitchContinuationSource,
|
|
536
|
+
selection: TSelection,
|
|
537
|
+
ctx: TContext,
|
|
538
|
+
) => void {
|
|
531
539
|
return (turn, selection, ctx) => {
|
|
532
540
|
deps.appendQueuedItem(
|
|
533
541
|
deps.createContinuationTurn({ turn, selection }),
|
|
@@ -544,7 +552,11 @@ export function createTelegramModelSwitchContinuationQueueRuntime<
|
|
|
544
552
|
allocateItemOrder: () => number;
|
|
545
553
|
allocateControlOrder: () => number;
|
|
546
554
|
appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
|
|
547
|
-
}): (
|
|
555
|
+
}): (
|
|
556
|
+
turn: TelegramModelSwitchContinuationSource,
|
|
557
|
+
selection: TSelection,
|
|
558
|
+
ctx: TContext,
|
|
559
|
+
) => void {
|
|
548
560
|
return createTelegramModelSwitchContinuationQueue<TContext, TSelection>({
|
|
549
561
|
createContinuationTurn: createTelegramModelSwitchContinuationTurnBuilder({
|
|
550
562
|
telegramPrefix: deps.telegramPrefix,
|
|
@@ -564,7 +576,7 @@ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
|
|
|
564
576
|
hasAbortHandler: () => boolean;
|
|
565
577
|
getActiveToolExecutions: () => number;
|
|
566
578
|
queueContinuation: (
|
|
567
|
-
turn:
|
|
579
|
+
turn: TelegramModelSwitchContinuationSource,
|
|
568
580
|
selection: TSelection,
|
|
569
581
|
ctx: TContext,
|
|
570
582
|
) => void;
|
|
@@ -573,15 +585,23 @@ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
|
|
|
573
585
|
|
|
574
586
|
export interface TelegramModelSwitchController<TContext, TSelection> {
|
|
575
587
|
canOfferInFlightSwitch: (ctx: TContext) => boolean;
|
|
576
|
-
stagePendingSwitch: (
|
|
588
|
+
stagePendingSwitch: (
|
|
589
|
+
selection: TSelection,
|
|
590
|
+
ctx: TContext,
|
|
591
|
+
continuationTurn?: TelegramModelSwitchContinuationSource,
|
|
592
|
+
) => void;
|
|
577
593
|
clearPendingSwitch: () => void;
|
|
578
594
|
queueContinuation: (
|
|
579
|
-
turn:
|
|
595
|
+
turn: TelegramModelSwitchContinuationSource,
|
|
580
596
|
selection: TSelection,
|
|
581
597
|
ctx: TContext,
|
|
582
598
|
) => void;
|
|
583
599
|
triggerPendingAbort: (ctx: TContext) => boolean;
|
|
584
|
-
restartInterruptedTurn: (
|
|
600
|
+
restartInterruptedTurn: (
|
|
601
|
+
selection: TSelection,
|
|
602
|
+
ctx: TContext,
|
|
603
|
+
continuationTurn?: TelegramModelSwitchContinuationSource,
|
|
604
|
+
) => boolean;
|
|
585
605
|
}
|
|
586
606
|
|
|
587
607
|
export interface TelegramModelSwitchControllerRuntimeDeps<
|
|
@@ -624,49 +644,62 @@ export function createTelegramModelSwitchControllerRuntime<
|
|
|
624
644
|
export function createTelegramModelSwitchController<TContext, TSelection>(
|
|
625
645
|
deps: TelegramModelSwitchControllerDeps<TContext, TSelection>,
|
|
626
646
|
): TelegramModelSwitchController<TContext, TSelection> {
|
|
647
|
+
let pendingContinuationTurn:
|
|
648
|
+
| TelegramModelSwitchContinuationSource
|
|
649
|
+
| undefined;
|
|
650
|
+
const triggerPendingAbort = (ctx: TContext): boolean => {
|
|
651
|
+
const turn = pendingContinuationTurn ?? deps.getActiveTurn();
|
|
652
|
+
if (
|
|
653
|
+
!shouldTriggerPendingTelegramModelSwitchAbort({
|
|
654
|
+
hasPendingModelSwitch: !!deps.getPendingModelSwitch(),
|
|
655
|
+
hasContinuationTurn: !!turn,
|
|
656
|
+
hasAbortHandler: deps.hasAbortHandler(),
|
|
657
|
+
activeToolExecutions: deps.getActiveToolExecutions(),
|
|
658
|
+
})
|
|
659
|
+
) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const selection = deps.getPendingModelSwitch();
|
|
663
|
+
const abort = deps.getAbortHandler();
|
|
664
|
+
if (!selection || !turn || !abort) return false;
|
|
665
|
+
pendingContinuationTurn = undefined;
|
|
666
|
+
deps.setPendingModelSwitch(undefined);
|
|
667
|
+
deps.queueContinuation(turn, selection, ctx);
|
|
668
|
+
abort();
|
|
669
|
+
return true;
|
|
670
|
+
};
|
|
627
671
|
return {
|
|
628
672
|
canOfferInFlightSwitch: (ctx) =>
|
|
629
|
-
|
|
673
|
+
canRestartAgentRunForTelegramModelSwitch({
|
|
630
674
|
isIdle: deps.isIdle(ctx),
|
|
631
|
-
hasActiveTelegramTurn: !!deps.getActiveTurn(),
|
|
632
675
|
hasAbortHandler: deps.hasAbortHandler(),
|
|
633
676
|
}),
|
|
634
|
-
stagePendingSwitch: (selection, ctx) => {
|
|
677
|
+
stagePendingSwitch: (selection, ctx, continuationTurn) => {
|
|
678
|
+
pendingContinuationTurn = deps.getActiveTurn() ?? continuationTurn;
|
|
635
679
|
deps.setPendingModelSwitch(selection);
|
|
636
|
-
|
|
680
|
+
try {
|
|
681
|
+
deps.updateStatus(ctx);
|
|
682
|
+
} finally {
|
|
683
|
+
triggerPendingAbort(ctx);
|
|
684
|
+
}
|
|
637
685
|
},
|
|
638
686
|
clearPendingSwitch: () => {
|
|
687
|
+
pendingContinuationTurn = undefined;
|
|
639
688
|
deps.setPendingModelSwitch(undefined);
|
|
640
689
|
},
|
|
641
690
|
queueContinuation: deps.queueContinuation,
|
|
642
|
-
triggerPendingAbort
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
hasActiveTelegramTurn: !!deps.getActiveTurn(),
|
|
647
|
-
hasAbortHandler: deps.hasAbortHandler(),
|
|
648
|
-
activeToolExecutions: deps.getActiveToolExecutions(),
|
|
649
|
-
})
|
|
650
|
-
) {
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
const selection = deps.getPendingModelSwitch();
|
|
654
|
-
const turn = deps.getActiveTurn();
|
|
655
|
-
const abort = deps.getAbortHandler();
|
|
656
|
-
if (!selection || !turn || !abort) return false;
|
|
657
|
-
deps.setPendingModelSwitch(undefined);
|
|
658
|
-
deps.queueContinuation(turn, selection, ctx);
|
|
659
|
-
abort();
|
|
660
|
-
return true;
|
|
661
|
-
},
|
|
662
|
-
restartInterruptedTurn: (selection, ctx) =>
|
|
663
|
-
restartTelegramModelSwitchContinuation({
|
|
664
|
-
activeTurn: deps.getActiveTurn(),
|
|
691
|
+
triggerPendingAbort,
|
|
692
|
+
restartInterruptedTurn: (selection, ctx, continuationTurn) => {
|
|
693
|
+
const restarted = restartTelegramModelSwitchContinuation({
|
|
694
|
+
activeTurn: deps.getActiveTurn() ?? continuationTurn,
|
|
665
695
|
abort: deps.getAbortHandler(),
|
|
666
696
|
selection,
|
|
667
697
|
queueContinuation: (turn, nextSelection) => {
|
|
668
698
|
deps.queueContinuation(turn, nextSelection, ctx);
|
|
669
699
|
},
|
|
670
|
-
})
|
|
700
|
+
});
|
|
701
|
+
if (restarted) pendingContinuationTurn = undefined;
|
|
702
|
+
return restarted;
|
|
703
|
+
},
|
|
671
704
|
};
|
|
672
705
|
}
|
package/lib/queue.ts
CHANGED
|
@@ -126,6 +126,8 @@ export interface PendingTelegramTurn extends TelegramQueueItemBase {
|
|
|
126
126
|
historyText: string;
|
|
127
127
|
priorityEmoji?: string;
|
|
128
128
|
reactionSuppressionEmoji?: string;
|
|
129
|
+
/** Emit the explicit aborted-turn notice when /next settles this active turn. */
|
|
130
|
+
announceNextAbortOnEnd?: boolean;
|
|
129
131
|
|
|
130
132
|
/** Turn should preferably be delivered as voice (mirror mode + user sent voice) */
|
|
131
133
|
voiceReplyPreferred?: boolean;
|
|
@@ -235,6 +237,8 @@ export interface TelegramActiveTurnStore<
|
|
|
235
237
|
has: () => boolean;
|
|
236
238
|
set: (turn: TTurn) => void;
|
|
237
239
|
clear: () => void;
|
|
240
|
+
markNextAbortAnnouncement: () => boolean;
|
|
241
|
+
clearNextAbortAnnouncement: () => boolean;
|
|
238
242
|
getChatId: () => number | undefined;
|
|
239
243
|
getTarget: () => TelegramQueueTarget | undefined;
|
|
240
244
|
getReplyToMessageId: () => number | undefined;
|
|
@@ -461,6 +465,16 @@ export function createTelegramActiveTurnStore<
|
|
|
461
465
|
clear: () => {
|
|
462
466
|
activeTurn = undefined;
|
|
463
467
|
},
|
|
468
|
+
markNextAbortAnnouncement: () => {
|
|
469
|
+
if (!activeTurn) return false;
|
|
470
|
+
activeTurn.announceNextAbortOnEnd = true;
|
|
471
|
+
return true;
|
|
472
|
+
},
|
|
473
|
+
clearNextAbortAnnouncement: () => {
|
|
474
|
+
if (!activeTurn?.announceNextAbortOnEnd) return false;
|
|
475
|
+
delete activeTurn.announceNextAbortOnEnd;
|
|
476
|
+
return true;
|
|
477
|
+
},
|
|
464
478
|
getChatId: () => activeTurn?.chatId,
|
|
465
479
|
getTarget: () =>
|
|
466
480
|
activeTurn?.target ? { ...activeTurn.target } : undefined,
|
|
@@ -1239,7 +1253,6 @@ export interface TelegramAgentStartHookRuntimeDeps<
|
|
|
1239
1253
|
export type TelegramAgentStartHookEvent = unknown;
|
|
1240
1254
|
|
|
1241
1255
|
export interface TelegramToolExecutionRuntimeDeps {
|
|
1242
|
-
hasActiveTurn: () => boolean;
|
|
1243
1256
|
getActiveToolExecutions: () => number;
|
|
1244
1257
|
setActiveToolExecutions: (count: number) => void;
|
|
1245
1258
|
}
|
|
@@ -1347,11 +1360,9 @@ export function createTelegramAgentStartHook<
|
|
|
1347
1360
|
}
|
|
1348
1361
|
|
|
1349
1362
|
export function getNextTelegramToolExecutionCount(options: {
|
|
1350
|
-
hasActiveTurn: boolean;
|
|
1351
1363
|
currentCount: number;
|
|
1352
1364
|
event: "start" | "end";
|
|
1353
1365
|
}): number {
|
|
1354
|
-
if (!options.hasActiveTurn) return options.currentCount;
|
|
1355
1366
|
if (options.event === "start") {
|
|
1356
1367
|
return options.currentCount + 1;
|
|
1357
1368
|
}
|
|
@@ -1363,7 +1374,6 @@ export function handleTelegramToolExecutionStartRuntime(
|
|
|
1363
1374
|
): void {
|
|
1364
1375
|
deps.setActiveToolExecutions(
|
|
1365
1376
|
getNextTelegramToolExecutionCount({
|
|
1366
|
-
hasActiveTurn: deps.hasActiveTurn(),
|
|
1367
1377
|
currentCount: deps.getActiveToolExecutions(),
|
|
1368
1378
|
event: "start",
|
|
1369
1379
|
}),
|
|
@@ -1373,15 +1383,13 @@ export function handleTelegramToolExecutionStartRuntime(
|
|
|
1373
1383
|
export function handleTelegramToolExecutionEndRuntime(
|
|
1374
1384
|
deps: TelegramToolExecutionEndRuntimeDeps,
|
|
1375
1385
|
): void {
|
|
1376
|
-
const hasActiveTurn = deps.hasActiveTurn();
|
|
1377
1386
|
deps.setActiveToolExecutions(
|
|
1378
1387
|
getNextTelegramToolExecutionCount({
|
|
1379
|
-
hasActiveTurn,
|
|
1380
1388
|
currentCount: deps.getActiveToolExecutions(),
|
|
1381
1389
|
event: "end",
|
|
1382
1390
|
}),
|
|
1383
1391
|
);
|
|
1384
|
-
|
|
1392
|
+
deps.triggerPendingModelSwitchAbort();
|
|
1385
1393
|
}
|
|
1386
1394
|
|
|
1387
1395
|
export type TelegramAgentLifecycleHooksRuntimeDeps<
|
|
@@ -1471,7 +1479,6 @@ export function createTelegramToolExecutionHooks<TContext>(
|
|
|
1471
1479
|
ctx: TContext,
|
|
1472
1480
|
): void => {
|
|
1473
1481
|
handleTelegramToolExecutionEndRuntime({
|
|
1474
|
-
hasActiveTurn: deps.hasActiveTurn,
|
|
1475
1482
|
getActiveToolExecutions: deps.getActiveToolExecutions,
|
|
1476
1483
|
setActiveToolExecutions: deps.setActiveToolExecutions,
|
|
1477
1484
|
triggerPendingModelSwitchAbort: () => {
|
|
@@ -1488,6 +1495,7 @@ export interface TelegramAgentEndPlan {
|
|
|
1488
1495
|
kind: "no-turn" | "aborted" | "error" | "text" | "attachments-only" | "empty";
|
|
1489
1496
|
shouldClearPreview: boolean;
|
|
1490
1497
|
shouldDispatchNext: boolean;
|
|
1498
|
+
shouldSendAbortMessage: boolean;
|
|
1491
1499
|
shouldSendErrorMessage: boolean;
|
|
1492
1500
|
shouldSendAttachmentNotice: boolean;
|
|
1493
1501
|
}
|
|
@@ -1680,6 +1688,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1680
1688
|
hasFinalText: boolean;
|
|
1681
1689
|
hasQueuedAttachments: boolean;
|
|
1682
1690
|
foldQueuedPromptsIntoHistory: boolean;
|
|
1691
|
+
announceNextAbortOnEnd?: boolean;
|
|
1683
1692
|
}): TelegramAgentEndPlan {
|
|
1684
1693
|
const shouldDispatchNext = shouldDispatchAfterTelegramAgentEnd({
|
|
1685
1694
|
hasTurn: options.hasTurn,
|
|
@@ -1691,6 +1700,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1691
1700
|
kind: "no-turn",
|
|
1692
1701
|
shouldClearPreview: false,
|
|
1693
1702
|
shouldDispatchNext,
|
|
1703
|
+
shouldSendAbortMessage: false,
|
|
1694
1704
|
shouldSendErrorMessage: false,
|
|
1695
1705
|
shouldSendAttachmentNotice: false,
|
|
1696
1706
|
};
|
|
@@ -1700,6 +1710,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1700
1710
|
kind: "aborted",
|
|
1701
1711
|
shouldClearPreview: true,
|
|
1702
1712
|
shouldDispatchNext,
|
|
1713
|
+
shouldSendAbortMessage: options.announceNextAbortOnEnd === true,
|
|
1703
1714
|
shouldSendErrorMessage: false,
|
|
1704
1715
|
shouldSendAttachmentNotice: false,
|
|
1705
1716
|
};
|
|
@@ -1709,6 +1720,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1709
1720
|
kind: "error",
|
|
1710
1721
|
shouldClearPreview: true,
|
|
1711
1722
|
shouldDispatchNext,
|
|
1723
|
+
shouldSendAbortMessage: false,
|
|
1712
1724
|
shouldSendErrorMessage: true,
|
|
1713
1725
|
shouldSendAttachmentNotice: false,
|
|
1714
1726
|
};
|
|
@@ -1718,6 +1730,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1718
1730
|
kind: "text",
|
|
1719
1731
|
shouldClearPreview: false,
|
|
1720
1732
|
shouldDispatchNext,
|
|
1733
|
+
shouldSendAbortMessage: false,
|
|
1721
1734
|
shouldSendErrorMessage: false,
|
|
1722
1735
|
shouldSendAttachmentNotice: false,
|
|
1723
1736
|
};
|
|
@@ -1727,6 +1740,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1727
1740
|
kind: "attachments-only",
|
|
1728
1741
|
shouldClearPreview: true,
|
|
1729
1742
|
shouldDispatchNext,
|
|
1743
|
+
shouldSendAbortMessage: false,
|
|
1730
1744
|
shouldSendErrorMessage: false,
|
|
1731
1745
|
shouldSendAttachmentNotice: true,
|
|
1732
1746
|
};
|
|
@@ -1735,6 +1749,7 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1735
1749
|
kind: "empty",
|
|
1736
1750
|
shouldClearPreview: true,
|
|
1737
1751
|
shouldDispatchNext,
|
|
1752
|
+
shouldSendAbortMessage: false,
|
|
1738
1753
|
shouldSendErrorMessage: false,
|
|
1739
1754
|
shouldSendAttachmentNotice: false,
|
|
1740
1755
|
};
|
|
@@ -1891,6 +1906,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1891
1906
|
hasFinalText: !!finalText || hasOutboundArtifacts,
|
|
1892
1907
|
hasQueuedAttachments: (turn?.queuedAttachments.length ?? 0) > 0,
|
|
1893
1908
|
foldQueuedPromptsIntoHistory: deps.foldQueuedPromptsIntoHistory,
|
|
1909
|
+
announceNextAbortOnEnd: turn?.announceNextAbortOnEnd === true,
|
|
1894
1910
|
});
|
|
1895
1911
|
if (!turn) {
|
|
1896
1912
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
@@ -2017,22 +2033,29 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
2017
2033
|
await clearTurnPreview();
|
|
2018
2034
|
if (!isDeliveryActive()) return;
|
|
2019
2035
|
}
|
|
2020
|
-
if (endPlan.shouldSendErrorMessage) {
|
|
2036
|
+
if (endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage) {
|
|
2021
2037
|
const errorMessage = assistant.errorMessage ||
|
|
2022
2038
|
"Telegram bridge: Pi failed while processing the request.";
|
|
2023
|
-
const isOperationAborted =
|
|
2024
|
-
"This operation was aborted";
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2039
|
+
const isOperationAborted = endPlan.shouldSendAbortMessage ||
|
|
2040
|
+
errorMessage.trim().replace(/\.$/, "") === "This operation was aborted";
|
|
2041
|
+
try {
|
|
2042
|
+
await deps.sendTextReply(
|
|
2043
|
+
turn.chatId,
|
|
2044
|
+
turn.replyToMessageId,
|
|
2045
|
+
isOperationAborted
|
|
2046
|
+
? "<b>⏹️ This operation was aborted.</b>"
|
|
2047
|
+
: errorMessage,
|
|
2048
|
+
{
|
|
2049
|
+
target: turn.target,
|
|
2050
|
+
...(isOperationAborted ? { parseMode: "HTML" as const } : {}),
|
|
2051
|
+
},
|
|
2052
|
+
);
|
|
2053
|
+
} catch (error) {
|
|
2054
|
+
if (!isOperationAborted) throw error;
|
|
2055
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2056
|
+
phase: "next-abort-announcement",
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2036
2059
|
if (!isDeliveryActive()) return;
|
|
2037
2060
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
2038
2061
|
return;
|
|
@@ -2180,7 +2203,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
2180
2203
|
};
|
|
2181
2204
|
if (
|
|
2182
2205
|
deps.scheduleActiveTurnDelivery &&
|
|
2183
|
-
(endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)
|
|
2206
|
+
(endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)
|
|
2184
2207
|
) {
|
|
2185
2208
|
deps.scheduleActiveTurnDelivery(deliverActiveTurn);
|
|
2186
2209
|
return;
|
|
@@ -3092,6 +3115,9 @@ export interface TelegramQueueDispatchControllerDeps<
|
|
|
3092
3115
|
) => boolean;
|
|
3093
3116
|
sendUserMessage: TelegramDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
3094
3117
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
3118
|
+
reconcileNextDispatchAnnouncementReplyOwnership?: (
|
|
3119
|
+
item: PendingTelegramTurn,
|
|
3120
|
+
) => void;
|
|
3095
3121
|
isQueueItemTransportActive?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
3096
3122
|
hasPendingInboundQueueMutationForItem?: (
|
|
3097
3123
|
item: TelegramQueueItem<TContext>,
|
|
@@ -3107,6 +3133,7 @@ export interface TelegramQueueDispatchControllerDeps<
|
|
|
3107
3133
|
export interface TelegramQueueDispatchController<TContext = unknown> {
|
|
3108
3134
|
dispatchNext: (ctx: TContext) => void;
|
|
3109
3135
|
requestNextDispatchAnnouncement: () => void;
|
|
3136
|
+
cancelNextDispatchAnnouncement: () => void;
|
|
3110
3137
|
}
|
|
3111
3138
|
|
|
3112
3139
|
export function executeTelegramQueueDispatchPlan<TContext = unknown>(
|
|
@@ -3161,6 +3188,8 @@ export function createTelegramQueueDispatchRuntime<TContext = unknown>(
|
|
|
3161
3188
|
commitPromptDispatch: deps.commitPromptDispatch,
|
|
3162
3189
|
sendUserMessage: deps.sendUserMessage,
|
|
3163
3190
|
onPromptDispatchFailure: deps.onPromptDispatchFailure,
|
|
3191
|
+
reconcileNextDispatchAnnouncementReplyOwnership:
|
|
3192
|
+
deps.reconcileNextDispatchAnnouncementReplyOwnership,
|
|
3164
3193
|
isQueueItemTransportActive: deps.isQueueItemTransportActive,
|
|
3165
3194
|
hasPendingInboundQueueMutationForItem:
|
|
3166
3195
|
deps.hasPendingInboundQueueMutationForItem,
|
|
@@ -3176,12 +3205,19 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
3176
3205
|
): TelegramQueueDispatchController<TContext> {
|
|
3177
3206
|
let controlDispatchPending = false;
|
|
3178
3207
|
let nextDispatchAnnouncementRequested = false;
|
|
3208
|
+
let nextDispatchAnnouncementGeneration = 0;
|
|
3179
3209
|
let nextDispatchAnnouncementAnchor: TelegramQueueItem<TContext> | undefined;
|
|
3180
3210
|
const controller: TelegramQueueDispatchController<TContext> = {
|
|
3181
3211
|
requestNextDispatchAnnouncement: () => {
|
|
3212
|
+
nextDispatchAnnouncementGeneration += 1;
|
|
3182
3213
|
nextDispatchAnnouncementRequested = true;
|
|
3183
3214
|
nextDispatchAnnouncementAnchor = undefined;
|
|
3184
3215
|
},
|
|
3216
|
+
cancelNextDispatchAnnouncement: () => {
|
|
3217
|
+
nextDispatchAnnouncementGeneration += 1;
|
|
3218
|
+
nextDispatchAnnouncementRequested = false;
|
|
3219
|
+
nextDispatchAnnouncementAnchor = undefined;
|
|
3220
|
+
},
|
|
3185
3221
|
dispatchNext: (ctx) => {
|
|
3186
3222
|
if (deps.hasDispatchContext && !deps.hasDispatchContext()) return;
|
|
3187
3223
|
if (controlDispatchPending) {
|
|
@@ -3393,6 +3429,7 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
3393
3429
|
if (dispatchPlan.kind === "prompt" && nextDispatchAnnouncementRequested) {
|
|
3394
3430
|
nextDispatchAnnouncementRequested = false;
|
|
3395
3431
|
controlDispatchPending = true;
|
|
3432
|
+
const announcementGeneration = nextDispatchAnnouncementGeneration;
|
|
3396
3433
|
const dispatchGeneration = deps.getDispatchGeneration?.();
|
|
3397
3434
|
deps.updateStatus(ctx);
|
|
3398
3435
|
void deps.sendTextReply(
|
|
@@ -3401,8 +3438,19 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
3401
3438
|
"<b>⏩ Dispatching next queued turn.</b>",
|
|
3402
3439
|
{ target: dispatchPlan.item.target },
|
|
3403
3440
|
).catch((error) => {
|
|
3404
|
-
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
3441
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
3442
|
+
phase: "next-announcement",
|
|
3443
|
+
});
|
|
3405
3444
|
}).finally(() => {
|
|
3445
|
+
try {
|
|
3446
|
+
deps.reconcileNextDispatchAnnouncementReplyOwnership?.(
|
|
3447
|
+
dispatchPlan.item,
|
|
3448
|
+
);
|
|
3449
|
+
} catch (error) {
|
|
3450
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
3451
|
+
phase: "next-announcement-reply-ownership",
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3406
3454
|
controlDispatchPending = false;
|
|
3407
3455
|
if (deps.hasDispatchContext && !deps.hasDispatchContext()) return;
|
|
3408
3456
|
if (
|
|
@@ -3410,6 +3458,10 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
3410
3458
|
deps.isDispatchGenerationActive &&
|
|
3411
3459
|
!deps.isDispatchGenerationActive(dispatchGeneration)
|
|
3412
3460
|
) return;
|
|
3461
|
+
if (announcementGeneration !== nextDispatchAnnouncementGeneration) {
|
|
3462
|
+
if (nextDispatchAnnouncementRequested) controller.dispatchNext(ctx);
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3413
3465
|
executePlan();
|
|
3414
3466
|
});
|
|
3415
3467
|
return;
|
package/lib/replies.ts
CHANGED
|
@@ -68,6 +68,7 @@ export function createReplyDedupRuntime(): ReplyDedupRuntime {
|
|
|
68
68
|
// --- Transport-level dedup ---
|
|
69
69
|
|
|
70
70
|
const lastRepliedToMessageIdByTarget = new Map<string, number>();
|
|
71
|
+
const replyDedupPreservedOnNextReset = new Map<string, number>();
|
|
71
72
|
let replyDedupGeneration = 0;
|
|
72
73
|
|
|
73
74
|
function getReplyDedupTargetKey(
|
|
@@ -83,6 +84,23 @@ function getReplyDedupTargetKey(
|
|
|
83
84
|
export function resetTransportReplyDedup(): void {
|
|
84
85
|
replyDedupGeneration += 1;
|
|
85
86
|
lastRepliedToMessageIdByTarget.clear();
|
|
87
|
+
for (const [key, messageId] of replyDedupPreservedOnNextReset) {
|
|
88
|
+
lastRepliedToMessageIdByTarget.set(key, messageId);
|
|
89
|
+
}
|
|
90
|
+
replyDedupPreservedOnNextReset.clear();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Keeps a successfully published transition notice as the first reply of the
|
|
94
|
+
* next agent turn. The following agent-start reset consumes this one-shot
|
|
95
|
+
* preservation, so later messages in that turn do not repeat the reply header. */
|
|
96
|
+
export function preserveTransportReplyDedupOnNextReset(
|
|
97
|
+
chatId: number,
|
|
98
|
+
messageId: number,
|
|
99
|
+
target?: TelegramTarget,
|
|
100
|
+
): void {
|
|
101
|
+
const key = getReplyDedupTargetKey(chatId, target);
|
|
102
|
+
if (lastRepliedToMessageIdByTarget.get(key) !== messageId) return;
|
|
103
|
+
replyDedupPreservedOnNextReset.set(key, messageId);
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
export function buildTelegramReplyParameters(
|
package/lib/routing.ts
CHANGED
|
@@ -627,6 +627,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
627
627
|
isContextActive?: (ctx: TContext) => boolean;
|
|
628
628
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
629
629
|
requestNextDispatchAnnouncement?: () => void;
|
|
630
|
+
cancelNextDispatchAnnouncement?: () => void;
|
|
630
631
|
requestDeferredDispatchNextQueuedTelegramTurn?: (
|
|
631
632
|
dispatch: (ctx: TContext) => void,
|
|
632
633
|
) => void;
|
|
@@ -1009,7 +1010,6 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1009
1010
|
updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
|
|
1010
1011
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
1011
1012
|
isIdle: deps.isIdle,
|
|
1012
|
-
hasActiveTelegramTurn: deps.activeTurnRuntime.has,
|
|
1013
1013
|
hasAbortHandler: deps.bridgeRuntime.abort.hasHandler,
|
|
1014
1014
|
getActiveToolExecutions:
|
|
1015
1015
|
deps.bridgeRuntime.lifecycle.getActiveToolExecutions,
|
|
@@ -2222,6 +2222,10 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2222
2222
|
isContextActive: deps.isContextActive,
|
|
2223
2223
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
2224
2224
|
requestNextDispatchAnnouncement: deps.requestNextDispatchAnnouncement,
|
|
2225
|
+
cancelNextTransitionAnnouncements: () => {
|
|
2226
|
+
deps.activeTurnRuntime.clearNextAbortAnnouncement();
|
|
2227
|
+
deps.cancelNextDispatchAnnouncement?.();
|
|
2228
|
+
},
|
|
2225
2229
|
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
2226
2230
|
deps.requestDeferredDispatchNextQueuedTelegramTurn,
|
|
2227
2231
|
startTypingLoop: deps.startTypingLoop,
|
|
@@ -2297,6 +2301,8 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2297
2301
|
},
|
|
2298
2302
|
getPromptTemplateCommands,
|
|
2299
2303
|
sendTextReply: deps.sendTextReply,
|
|
2304
|
+
markActiveTurnNextAbortAnnouncement:
|
|
2305
|
+
deps.activeTurnRuntime.markNextAbortAnnouncement,
|
|
2300
2306
|
getActiveTurnReply: () => {
|
|
2301
2307
|
const activeTurn = deps.activeTurnRuntime.get();
|
|
2302
2308
|
if (!activeTurn) return undefined;
|