@llblab/pi-telegram 0.43.1 → 0.44.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 +1 -1
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/multi-instance-bus.md +4 -0
- package/docs/outbound.md +27 -5
- package/docs/public-api.md +1 -0
- package/index.ts +24 -19
- package/lib/activity-verbosity.ts +43 -25
- package/lib/activity.ts +79 -6
- package/lib/bindings.ts +110 -91
- package/lib/config.ts +1 -1
- package/lib/delivery.ts +18 -18
- package/lib/lifecycle.ts +14 -3
- package/lib/menu-settings.ts +2 -2
- package/lib/outbound-attachments.ts +41 -37
- package/lib/outbound-voice.ts +39 -42
- package/lib/outbound.ts +28 -17
- package/lib/preview.ts +134 -73
- package/lib/queue.ts +113 -72
- package/lib/replies.ts +46 -38
- package/lib/routing.ts +192 -58
- package/lib/telegram-api.ts +36 -3
- package/lib/updates.ts +27 -35
- package/package.json +1 -1
package/lib/activity.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface TelegramActivityEnvelope {
|
|
|
32
32
|
sequence: number;
|
|
33
33
|
source: TelegramActivitySource;
|
|
34
34
|
target?: TelegramActivityTarget;
|
|
35
|
+
replyToMessageId?: number;
|
|
35
36
|
timestamp: number;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -422,8 +423,8 @@ export function createTelegramActivityBridgeRuntime(deps: {
|
|
|
422
423
|
recordInputSource(source) {
|
|
423
424
|
getRuntime()?.recordInputSource(source);
|
|
424
425
|
},
|
|
425
|
-
onAgentStart(target) {
|
|
426
|
-
getRuntime()?.onAgentStart(target);
|
|
426
|
+
onAgentStart(target, replyToMessageId) {
|
|
427
|
+
getRuntime()?.onAgentStart(target, replyToMessageId);
|
|
427
428
|
},
|
|
428
429
|
onAssistantEvent(event) {
|
|
429
430
|
getRuntime()?.onAssistantEvent(event);
|
|
@@ -492,7 +493,7 @@ export type TelegramAssistantStreamEvent =
|
|
|
492
493
|
export interface TelegramActivityRuntime {
|
|
493
494
|
onSessionStart?: () => void;
|
|
494
495
|
recordInputSource: (source: TelegramActivityInputSource) => void;
|
|
495
|
-
onAgentStart: (activeTelegramTarget?: TelegramActivityTarget) => void;
|
|
496
|
+
onAgentStart: (activeTelegramTarget?: TelegramActivityTarget, replyToMessageId?: number) => void;
|
|
496
497
|
onAssistantEvent: (event: TelegramAssistantStreamEvent) => void;
|
|
497
498
|
onAssistantMessageEnd: (stopReason?: string) => void;
|
|
498
499
|
onToolStart: (event: {
|
|
@@ -549,6 +550,7 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
549
550
|
let activityId: string | undefined;
|
|
550
551
|
let activitySource: TelegramActivitySource = "unknown";
|
|
551
552
|
let activityTarget: TelegramActivityTarget | undefined;
|
|
553
|
+
let activityReplyToMessageId: number | undefined;
|
|
552
554
|
let sequence = 0;
|
|
553
555
|
let pendingInputSource: TelegramActivityInputSource = "unknown";
|
|
554
556
|
let pendingAssistantSegment: PendingAssistantSegment | undefined;
|
|
@@ -584,6 +586,7 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
584
586
|
sequence,
|
|
585
587
|
source: activitySource,
|
|
586
588
|
...(activityTarget ? { target: activityTarget } : {}),
|
|
589
|
+
...(activityReplyToMessageId !== undefined ? { replyToMessageId: activityReplyToMessageId } : {}),
|
|
587
590
|
timestamp: now(),
|
|
588
591
|
} as TelegramActivityEvent;
|
|
589
592
|
try {
|
|
@@ -610,6 +613,7 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
610
613
|
activityId = undefined;
|
|
611
614
|
activitySource = "unknown";
|
|
612
615
|
activityTarget = undefined;
|
|
616
|
+
activityReplyToMessageId = undefined;
|
|
613
617
|
sequence = 0;
|
|
614
618
|
pendingAssistantSegment = undefined;
|
|
615
619
|
compactionInProgress = false;
|
|
@@ -627,9 +631,10 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
627
631
|
recordInputSource(source) {
|
|
628
632
|
pendingInputSource = source;
|
|
629
633
|
},
|
|
630
|
-
onAgentStart(activeTelegramTarget) {
|
|
634
|
+
onAgentStart(activeTelegramTarget, replyToMessageId) {
|
|
631
635
|
abandonCompaction();
|
|
632
636
|
ensureActivity(activeTelegramTarget);
|
|
637
|
+
activityReplyToMessageId = activitySource === "telegram" ? replyToMessageId : undefined;
|
|
633
638
|
emit({ type: "agent-start" });
|
|
634
639
|
},
|
|
635
640
|
onAssistantEvent(event) {
|
|
@@ -738,6 +743,63 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
738
743
|
};
|
|
739
744
|
}
|
|
740
745
|
|
|
746
|
+
// --- Ordered Bridge-Owned Publication ---
|
|
747
|
+
|
|
748
|
+
export interface TelegramActivityPublicationReservation {
|
|
749
|
+
publish: (task: () => Promise<void>) => Promise<void>;
|
|
750
|
+
cancel: () => void;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export interface TelegramActivityPublicationRuntime {
|
|
754
|
+
enqueue: (task: () => Promise<void>) => Promise<void>;
|
|
755
|
+
reserve: () => TelegramActivityPublicationReservation;
|
|
756
|
+
reset: () => void;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export function createTelegramActivityPublicationRuntime(): TelegramActivityPublicationRuntime {
|
|
760
|
+
let generation = 0;
|
|
761
|
+
let tail = Promise.resolve();
|
|
762
|
+
const pending = new Set<() => void>();
|
|
763
|
+
const reserve = (): TelegramActivityPublicationReservation => {
|
|
764
|
+
const admittedGeneration = generation;
|
|
765
|
+
let state: "pending" | "published" | "cancelled" = "pending";
|
|
766
|
+
let resolve!: (task: (() => Promise<void>) | undefined) => void;
|
|
767
|
+
const ready = new Promise<(() => Promise<void>) | undefined>((accept) => { resolve = accept; });
|
|
768
|
+
const cancel = () => {
|
|
769
|
+
if (state !== "pending") return;
|
|
770
|
+
state = "cancelled";
|
|
771
|
+
pending.delete(cancel);
|
|
772
|
+
resolve(undefined);
|
|
773
|
+
};
|
|
774
|
+
pending.add(cancel);
|
|
775
|
+
const result = tail.then(async () => {
|
|
776
|
+
const task = await ready;
|
|
777
|
+
if (admittedGeneration === generation && task) await task();
|
|
778
|
+
});
|
|
779
|
+
tail = result.catch(() => {});
|
|
780
|
+
return {
|
|
781
|
+
publish(task) {
|
|
782
|
+
if (state === "cancelled") return result;
|
|
783
|
+
if (state === "published") return Promise.reject(new Error("Publication reservation already published."));
|
|
784
|
+
state = "published";
|
|
785
|
+
pending.delete(cancel);
|
|
786
|
+
resolve(task);
|
|
787
|
+
return result;
|
|
788
|
+
},
|
|
789
|
+
cancel,
|
|
790
|
+
};
|
|
791
|
+
};
|
|
792
|
+
return {
|
|
793
|
+
reserve,
|
|
794
|
+
enqueue: (task) => reserve().publish(task),
|
|
795
|
+
reset() {
|
|
796
|
+
generation += 1;
|
|
797
|
+
for (const cancel of pending) cancel();
|
|
798
|
+
tail = Promise.resolve();
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
741
803
|
// --- Public Assistant Output Projection ---
|
|
742
804
|
|
|
743
805
|
export interface TelegramAssistantOutputRuntime {
|
|
@@ -747,7 +809,14 @@ export interface TelegramAssistantOutputRuntime {
|
|
|
747
809
|
stop: () => void;
|
|
748
810
|
}
|
|
749
811
|
|
|
812
|
+
export interface TelegramAssistantOutputPreparation {
|
|
813
|
+
wait: () => Promise<void>;
|
|
814
|
+
settle: () => void;
|
|
815
|
+
}
|
|
816
|
+
|
|
750
817
|
export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(deps: {
|
|
818
|
+
prepareSend?: (event: TelegramAssistantSegmentEvent) => TelegramAssistantOutputPreparation | undefined;
|
|
819
|
+
enqueue?: TelegramActivityPublicationRuntime["enqueue"];
|
|
751
820
|
captureAuthority?: () => TAuthority;
|
|
752
821
|
isAuthorityActive?: (authority: TAuthority) => boolean;
|
|
753
822
|
canDeliver: (event: TelegramAssistantSegmentEvent) => boolean;
|
|
@@ -785,7 +854,9 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
785
854
|
admitted.add(key);
|
|
786
855
|
const admittedGeneration = generation;
|
|
787
856
|
const admittedAuthority = deps.captureAuthority?.();
|
|
788
|
-
|
|
857
|
+
const preparation = deps.prepareSend?.(event);
|
|
858
|
+
const enqueue = deps.enqueue ?? ((task: () => Promise<void>) => tail.then(task));
|
|
859
|
+
tail = enqueue(async () => {
|
|
789
860
|
const isAdmittedAuthorityActive = () =>
|
|
790
861
|
running &&
|
|
791
862
|
generation === admittedGeneration &&
|
|
@@ -794,6 +865,8 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
794
865
|
deps.isAuthorityActive(admittedAuthority as TAuthority));
|
|
795
866
|
if (!isAdmittedAuthorityActive() || !deps.canDeliver(event)) return;
|
|
796
867
|
try {
|
|
868
|
+
if (preparation) await preparation.wait();
|
|
869
|
+
if (!isAdmittedAuthorityActive() || !deps.canDeliver(event)) return;
|
|
797
870
|
await deps.send(
|
|
798
871
|
event,
|
|
799
872
|
admittedAuthority as TAuthority,
|
|
@@ -802,7 +875,7 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
802
875
|
} catch (error) {
|
|
803
876
|
deps.recordFailure?.(event, error);
|
|
804
877
|
}
|
|
805
|
-
});
|
|
878
|
+
}).finally(() => preparation?.settle());
|
|
806
879
|
},
|
|
807
880
|
waitForIdle() {
|
|
808
881
|
return tail;
|
package/lib/bindings.ts
CHANGED
|
@@ -353,6 +353,8 @@ export function createTelegramAssistantOutputBindingRuntime<
|
|
|
353
353
|
typeof OutboundHandlers.createTelegramAssistantOutputSender<TTransportStamp>
|
|
354
354
|
>[0];
|
|
355
355
|
waitForActivityIdle?: () => Promise<void>;
|
|
356
|
+
prepareTelegramPreview?: () => Activity.TelegramAssistantOutputPreparation | undefined;
|
|
357
|
+
enqueue?: Activity.TelegramActivityPublicationRuntime["enqueue"];
|
|
356
358
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
357
359
|
}): TelegramAssistantOutputBindingRuntime<TTransportStamp> {
|
|
358
360
|
const authority = Routing.createTelegramAssistantOutputAuthorityRuntime(
|
|
@@ -364,6 +366,8 @@ export function createTelegramAssistantOutputBindingRuntime<
|
|
|
364
366
|
);
|
|
365
367
|
const runtime = Activity.createTelegramAssistantOutputRuntime({
|
|
366
368
|
...authority,
|
|
369
|
+
enqueue: deps.enqueue,
|
|
370
|
+
prepareSend: (event) => event.source === "telegram" ? deps.prepareTelegramPreview?.() : undefined,
|
|
367
371
|
async send(event, authority, isAuthorityActive) {
|
|
368
372
|
await deps.waitForActivityIdle?.();
|
|
369
373
|
if (!isAuthorityActive()) return;
|
|
@@ -390,7 +394,14 @@ type TelegramAssistantOutputAuthority<TTransportStamp> = ReturnType<
|
|
|
390
394
|
Routing.TelegramAssistantOutputAuthorityRuntime<TTransportStamp>["captureAuthority"]
|
|
391
395
|
>;
|
|
392
396
|
|
|
397
|
+
export interface TelegramBridgePublicationRuntime {
|
|
398
|
+
enqueue: Activity.TelegramActivityPublicationRuntime["enqueue"];
|
|
399
|
+
reserve: Activity.TelegramActivityPublicationRuntime["reserve"];
|
|
400
|
+
capture: () => { target?: Queue.TelegramQueueTarget; isCurrent: () => boolean };
|
|
401
|
+
}
|
|
402
|
+
|
|
393
403
|
export interface TelegramActivityBindingRuntime {
|
|
404
|
+
publicationRuntime: TelegramBridgePublicationRuntime;
|
|
394
405
|
activityRuntime: Activity.TelegramActivityRuntime;
|
|
395
406
|
activityVerbosityRuntime: ActivityVerbosity.TelegramActivityVerbosityRuntime;
|
|
396
407
|
assistantOutputRuntime: Activity.TelegramAssistantOutputRuntime;
|
|
@@ -403,7 +414,7 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
403
414
|
Parameters<
|
|
404
415
|
typeof createTelegramAssistantOutputBindingRuntime<TTransportStamp>
|
|
405
416
|
>[0],
|
|
406
|
-
"waitForActivityIdle"
|
|
417
|
+
"waitForActivityIdle" | "enqueue"
|
|
407
418
|
>;
|
|
408
419
|
activityVerbosity: Omit<
|
|
409
420
|
Parameters<
|
|
@@ -411,19 +422,19 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
411
422
|
TelegramAssistantOutputAuthority<TTransportStamp>
|
|
412
423
|
>
|
|
413
424
|
>[0],
|
|
414
|
-
"captureAuthority" | "isAuthorityActive" | "recordFailure"
|
|
425
|
+
"captureAuthority" | "isAuthorityActive" | "recordFailure" | "enqueue"
|
|
415
426
|
>;
|
|
416
427
|
}): TelegramActivityBindingRuntime {
|
|
417
|
-
const
|
|
418
|
-
ActivityVerbosity.createTelegramActivityVerbosityBinding();
|
|
428
|
+
const publication = Activity.createTelegramActivityPublicationRuntime();
|
|
419
429
|
const assistantOutputBinding =
|
|
420
430
|
createTelegramAssistantOutputBindingRuntime({
|
|
421
431
|
...deps.assistantOutput,
|
|
422
|
-
|
|
432
|
+
enqueue: publication.enqueue,
|
|
423
433
|
});
|
|
424
434
|
const activityVerbosityRuntime =
|
|
425
435
|
ActivityVerbosity.createTelegramActivityVerbosityRuntime({
|
|
426
436
|
...deps.activityVerbosity,
|
|
437
|
+
enqueue: publication.enqueue,
|
|
427
438
|
captureAuthority: assistantOutputBinding.authority.captureAuthority,
|
|
428
439
|
isAuthorityActive: assistantOutputBinding.authority.isAuthorityActive,
|
|
429
440
|
recordFailure(operation, event, error) {
|
|
@@ -434,7 +445,6 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
434
445
|
});
|
|
435
446
|
},
|
|
436
447
|
});
|
|
437
|
-
activityVerbosityBinding.bind(activityVerbosityRuntime);
|
|
438
448
|
const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
|
|
439
449
|
generation: deps.generation,
|
|
440
450
|
observeEvent(event) {
|
|
@@ -450,9 +460,30 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
450
460
|
},
|
|
451
461
|
});
|
|
452
462
|
return {
|
|
453
|
-
activityRuntime
|
|
463
|
+
activityRuntime: {
|
|
464
|
+
...activityRuntime,
|
|
465
|
+
onSessionStart() {
|
|
466
|
+
publication.reset();
|
|
467
|
+
activityRuntime.onSessionStart?.();
|
|
468
|
+
},
|
|
469
|
+
onSessionShutdown() {
|
|
470
|
+
publication.reset();
|
|
471
|
+
activityRuntime.onSessionShutdown();
|
|
472
|
+
},
|
|
473
|
+
},
|
|
454
474
|
activityVerbosityRuntime,
|
|
455
475
|
assistantOutputRuntime: assistantOutputBinding.runtime,
|
|
476
|
+
publicationRuntime: {
|
|
477
|
+
enqueue: publication.enqueue,
|
|
478
|
+
reserve: publication.reserve,
|
|
479
|
+
capture() {
|
|
480
|
+
const authority = assistantOutputBinding.authority.captureAuthority();
|
|
481
|
+
return {
|
|
482
|
+
target: authority.target ? { ...authority.target } : undefined,
|
|
483
|
+
isCurrent: () => assistantOutputBinding.authority.isAuthorityActive(authority),
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
},
|
|
456
487
|
};
|
|
457
488
|
}
|
|
458
489
|
|
|
@@ -680,6 +711,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
680
711
|
|
|
681
712
|
interface TelegramLifecycleBindingDeps {
|
|
682
713
|
pi: Pi.ExtensionAPI;
|
|
714
|
+
publicationRuntime: TelegramBridgePublicationRuntime;
|
|
683
715
|
activityRuntime: Activity.TelegramActivityRuntime;
|
|
684
716
|
activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
|
|
685
717
|
assistantOutputRuntime: Pick<
|
|
@@ -748,6 +780,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
748
780
|
Keyboard.TelegramInlineKeyboardMarkup
|
|
749
781
|
>["sendGuestReply"]
|
|
750
782
|
>;
|
|
783
|
+
preparePreviewDelivery?: Queue.TelegramAgentEndRuntimeDeps<Queue.PendingTelegramTurn>["preparePreviewDelivery"];
|
|
751
784
|
finalizeMarkdownPreview: Queue.TelegramAgentEndHookRuntimeDeps<
|
|
752
785
|
Queue.PendingTelegramTurn,
|
|
753
786
|
Pi.ExtensionContext,
|
|
@@ -770,6 +803,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
770
803
|
|
|
771
804
|
export function registerTelegramLifecycleRuntimeHooks({
|
|
772
805
|
pi,
|
|
806
|
+
publicationRuntime,
|
|
773
807
|
activityRuntime,
|
|
774
808
|
activityVerbosityRuntime,
|
|
775
809
|
assistantOutputRuntime,
|
|
@@ -798,6 +832,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
798
832
|
answerGuestQuery,
|
|
799
833
|
deleteMessage,
|
|
800
834
|
sendGuestReply,
|
|
835
|
+
preparePreviewDelivery,
|
|
801
836
|
finalizeMarkdownPreview,
|
|
802
837
|
proactivePushTargetGetter,
|
|
803
838
|
getAssistantRenderingMode,
|
|
@@ -936,24 +971,19 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
936
971
|
{ replyToPrompt: false },
|
|
937
972
|
);
|
|
938
973
|
};
|
|
939
|
-
let
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
timer.unref?.();
|
|
947
|
-
});
|
|
948
|
-
await task();
|
|
949
|
-
})().catch((error) => {
|
|
950
|
-
recordRuntimeEvent("delivery", error, {
|
|
951
|
-
phase: "agent-end-background-delivery",
|
|
952
|
-
});
|
|
953
|
-
});
|
|
974
|
+
let pendingFinalPublication: {
|
|
975
|
+
turn: Queue.PendingTelegramTurn;
|
|
976
|
+
reservation: Activity.TelegramActivityPublicationReservation;
|
|
977
|
+
} | undefined;
|
|
978
|
+
const cancelPendingFinalPublication = (): void => {
|
|
979
|
+
pendingFinalPublication?.reservation.cancel();
|
|
980
|
+
pendingFinalPublication = undefined;
|
|
954
981
|
};
|
|
955
|
-
const
|
|
956
|
-
|
|
982
|
+
const recordPublicationFailure = (error: unknown): void => {
|
|
983
|
+
recordRuntimeEvent("delivery", error, { phase: "agent-end-background-delivery" });
|
|
984
|
+
};
|
|
985
|
+
const scheduleActiveTurnDelivery = (task: () => Promise<void>): void => {
|
|
986
|
+
void publicationRuntime.enqueue(task).catch(recordPublicationFailure);
|
|
957
987
|
};
|
|
958
988
|
const agentLifecycleHooks = Queue.createTelegramAgentLifecycleHooks<
|
|
959
989
|
Queue.PendingTelegramTurn,
|
|
@@ -989,14 +1019,23 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
989
1019
|
isSessionActive: isSessionContextActive,
|
|
990
1020
|
isTurnTransportActive,
|
|
991
1021
|
waitForTypingIdle: typing.waitForIdle,
|
|
992
|
-
async waitForActivityIdle() {
|
|
993
|
-
await activityVerbosityRuntime?.waitForIdle();
|
|
994
|
-
await assistantOutputRuntime.waitForIdle();
|
|
995
|
-
},
|
|
996
1022
|
dispatchNextQueuedTelegramTurn,
|
|
997
1023
|
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
998
1024
|
deferredQueueDispatchRuntime.request,
|
|
999
1025
|
scheduleActiveTurnDelivery,
|
|
1026
|
+
reserveActiveTurnDelivery() {
|
|
1027
|
+
const pending = pendingFinalPublication;
|
|
1028
|
+
pendingFinalPublication = undefined;
|
|
1029
|
+
const matches = pending?.turn === activeTurnRuntime.get();
|
|
1030
|
+
if (!matches) pending?.reservation.cancel();
|
|
1031
|
+
const reservation = matches && pending ? pending.reservation : publicationRuntime.reserve();
|
|
1032
|
+
return {
|
|
1033
|
+
schedule: (task) => { void reservation.publish(task).catch(recordPublicationFailure); },
|
|
1034
|
+
cancel: reservation.cancel,
|
|
1035
|
+
};
|
|
1036
|
+
},
|
|
1037
|
+
preparePreviewDelivery,
|
|
1038
|
+
preparePreviewClear: previewRuntime.prepareClear,
|
|
1000
1039
|
clearPreview: previewRuntime.clear,
|
|
1001
1040
|
setPreviewPendingText: previewRuntime.setPendingText,
|
|
1002
1041
|
finalizeMarkdownPreview,
|
|
@@ -1018,6 +1057,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1018
1057
|
Lifecycle.setResetTransportReplyDedup(Replies.resetTransportReplyDedup);
|
|
1019
1058
|
const agentStartWithDedupReset = Lifecycle.createAgentStartDedupHook(
|
|
1020
1059
|
agentLifecycleHooks.onAgentStart,
|
|
1060
|
+
scheduleActiveTurnDelivery,
|
|
1021
1061
|
);
|
|
1022
1062
|
let uiPromptActive = false;
|
|
1023
1063
|
const startAgentActivityTypingLoop = (ctx: Pi.ExtensionContext): boolean => {
|
|
@@ -1038,25 +1078,24 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1038
1078
|
};
|
|
1039
1079
|
let observedAutomaticCompaction = false;
|
|
1040
1080
|
let agentWorkActive = false;
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const sendCompactionNotice = async (text: string): Promise<void> => {
|
|
1081
|
+
const prepareCompactionNotice = (text: string, ctx: Pi.ExtensionContext): (() => Promise<void>) => {
|
|
1082
|
+
const authority = publicationRuntime.capture();
|
|
1044
1083
|
const turn = activeTurnRuntime.get();
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1084
|
+
const selectedTarget = turn?.target ?? authority.target;
|
|
1085
|
+
const target = selectedTarget ? { ...selectedTarget } : undefined;
|
|
1086
|
+
const replyToMessageId = turn?.replyToMessageId;
|
|
1087
|
+
return async () => {
|
|
1088
|
+
if (!target || !isSessionContextActive(ctx) || !authority.isCurrent()) return;
|
|
1089
|
+
if (turn && isTurnTransportActive?.(turn) === false) return;
|
|
1090
|
+
try {
|
|
1091
|
+
await sendMarkdownReply(target.chatId, replyToMessageId, text, { target });
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
recordRuntimeEvent("delivery", error, { phase: "compaction-notice" });
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1056
1096
|
};
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
for (const notice of notices) await sendCompactionNotice(notice);
|
|
1097
|
+
const sendCompactionNotice = (text: string, ctx: Pi.ExtensionContext): void => {
|
|
1098
|
+
scheduleActiveTurnDelivery(prepareCompactionNotice(text, ctx));
|
|
1060
1099
|
};
|
|
1061
1100
|
const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
|
|
1062
1101
|
isContextActive: isSessionContextActive,
|
|
@@ -1070,7 +1109,6 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1070
1109
|
recordRuntimeEvent,
|
|
1071
1110
|
onCompactionAbandoned: () => {
|
|
1072
1111
|
observedAutomaticCompaction = false;
|
|
1073
|
-
deferredAutomaticCompactionNotices.length = 0;
|
|
1074
1112
|
activityRuntime.onCompactionAbandoned();
|
|
1075
1113
|
},
|
|
1076
1114
|
});
|
|
@@ -1091,6 +1129,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1091
1129
|
activityRuntime.recordInputSource(event.source ?? "unknown");
|
|
1092
1130
|
},
|
|
1093
1131
|
async onSessionStart(event, ctx) {
|
|
1132
|
+
cancelPendingFinalPublication();
|
|
1094
1133
|
previewRuntime.invalidate();
|
|
1095
1134
|
assistantOutputRuntime.start();
|
|
1096
1135
|
activityRuntime.onSessionStart?.();
|
|
@@ -1106,9 +1145,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1106
1145
|
assistantOutputRuntime.stop();
|
|
1107
1146
|
observedAutomaticCompaction = false;
|
|
1108
1147
|
agentWorkActive = false;
|
|
1109
|
-
|
|
1148
|
+
cancelPendingFinalPublication();
|
|
1110
1149
|
uiPromptActive = false;
|
|
1111
|
-
deferredAutomaticCompactionNotices.length = 0;
|
|
1112
1150
|
compactionObserver.onSessionShutdown();
|
|
1113
1151
|
if (event.reason === "quit" && disconnectOnQuit) {
|
|
1114
1152
|
try {
|
|
@@ -1129,19 +1167,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1129
1167
|
if (shouldNotify) observedAutomaticCompaction = true;
|
|
1130
1168
|
activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
|
|
1131
1169
|
compactionObserver.onSessionBeforeCompact(event, ctx);
|
|
1132
|
-
if (shouldNotify)
|
|
1133
|
-
if (terminalAssistantMessagePendingDelivery) {
|
|
1134
|
-
deferredAutomaticCompactionNotices.push(
|
|
1135
|
-
Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN,
|
|
1136
|
-
);
|
|
1137
|
-
} else {
|
|
1138
|
-
await waitForActiveTurnDelivery();
|
|
1139
|
-
if (!isSessionContextActive(ctx)) return;
|
|
1140
|
-
await sendCompactionNotice(
|
|
1141
|
-
Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN,
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1170
|
+
if (shouldNotify) sendCompactionNotice(Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN, ctx);
|
|
1145
1171
|
},
|
|
1146
1172
|
async onSessionCompact(event, ctx) {
|
|
1147
1173
|
if (!isSessionContextActive(ctx)) return;
|
|
@@ -1149,40 +1175,26 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1149
1175
|
compactionObserver.onSessionCompact(event, ctx);
|
|
1150
1176
|
if (observedAutomaticCompaction) {
|
|
1151
1177
|
observedAutomaticCompaction = false;
|
|
1152
|
-
|
|
1153
|
-
deferredAutomaticCompactionNotices.push(
|
|
1154
|
-
Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN,
|
|
1155
|
-
);
|
|
1156
|
-
} else {
|
|
1157
|
-
await sendCompactionNotice(
|
|
1158
|
-
Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN,
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1178
|
+
sendCompactionNotice(Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN, ctx);
|
|
1161
1179
|
}
|
|
1162
1180
|
},
|
|
1163
1181
|
async onSessionCompactFailed(event, ctx) {
|
|
1164
1182
|
if (!isSessionContextActive(ctx)) return;
|
|
1165
1183
|
const shouldNotify = observedAutomaticCompaction;
|
|
1166
|
-
const deferredNotices = deferredAutomaticCompactionNotices.splice(0);
|
|
1167
|
-
const shouldDefer =
|
|
1168
|
-
deferredNotices.length > 0 || terminalAssistantMessagePendingDelivery;
|
|
1169
1184
|
compactionObserver.onSessionCompactFailed(event, ctx);
|
|
1170
1185
|
if (!shouldNotify) return;
|
|
1171
1186
|
const notice = event.aborted
|
|
1172
1187
|
? "**⚠️ Compaction cancelled.**"
|
|
1173
1188
|
: "**⚠️ Compaction failed.**";
|
|
1174
|
-
|
|
1175
|
-
deferredAutomaticCompactionNotices.push(...deferredNotices, notice);
|
|
1176
|
-
} else {
|
|
1177
|
-
await sendCompactionNotice(notice);
|
|
1178
|
-
}
|
|
1189
|
+
sendCompactionNotice(notice, ctx);
|
|
1179
1190
|
},
|
|
1180
1191
|
async onAgentStart(event, ctx) {
|
|
1181
1192
|
if (!isSessionContextActive(ctx)) return;
|
|
1182
1193
|
agentWorkActive = true;
|
|
1183
|
-
|
|
1194
|
+
cancelPendingFinalPublication();
|
|
1184
1195
|
await agentStartWithDedupReset(event, ctx);
|
|
1185
|
-
|
|
1196
|
+
const turn = activeTurnRuntime.get();
|
|
1197
|
+
activityRuntime.onAgentStart(turn?.target, turn?.replyToMessageId);
|
|
1186
1198
|
startAgentActivityTypingLoop(ctx);
|
|
1187
1199
|
},
|
|
1188
1200
|
async onToolExecutionStart(event, ctx) {
|
|
@@ -1228,13 +1240,16 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1228
1240
|
onMessageEnd(event, ctx) {
|
|
1229
1241
|
if (!isSessionContextActive(ctx)) return;
|
|
1230
1242
|
if (event.message.role === "assistant") {
|
|
1243
|
+
previewRuntime.seal();
|
|
1231
1244
|
activityRuntime.onAssistantMessageEnd(event.message.stopReason);
|
|
1232
1245
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1246
|
+
if (event.message.role !== "assistant" || event.message.stopReason === "toolUse" || event.message.stopReason === "aborted") return;
|
|
1247
|
+
const turn = activeTurnRuntime.get();
|
|
1248
|
+
if (!turn || turn.guestQueryId || pendingFinalPublication?.turn === turn) return;
|
|
1249
|
+
cancelPendingFinalPublication();
|
|
1250
|
+
const assistant = Replies.extractLatestAssistantMessageText([event.message]);
|
|
1251
|
+
if (!assistant.text && assistant.stopReason !== "error" && turn.queuedAttachments.length === 0) return;
|
|
1252
|
+
pendingFinalPublication = { turn, reservation: publicationRuntime.reserve() };
|
|
1238
1253
|
},
|
|
1239
1254
|
onUiPromptStart(event, ctx) {
|
|
1240
1255
|
if (!isSessionContextActive(ctx)) return;
|
|
@@ -1254,18 +1269,22 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1254
1269
|
},
|
|
1255
1270
|
async onAgentEnd(event, ctx) {
|
|
1256
1271
|
if (!isSessionContextActive(ctx)) return;
|
|
1272
|
+
if (pendingFinalPublication && pendingFinalPublication.turn !== activeTurnRuntime.get()) {
|
|
1273
|
+
cancelPendingFinalPublication();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1257
1276
|
activityRuntime.onAgentEnd();
|
|
1258
1277
|
await agentLifecycleHooks.onAgentEnd(event, ctx);
|
|
1259
1278
|
},
|
|
1260
1279
|
async onAgentSettled(event, ctx) {
|
|
1261
1280
|
if (!isSessionContextActive(ctx)) return;
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
await
|
|
1265
|
-
|
|
1266
|
-
|
|
1281
|
+
const pending = pendingFinalPublication;
|
|
1282
|
+
try {
|
|
1283
|
+
await agentLifecycleHooks.onAgentSettled(event, ctx);
|
|
1284
|
+
} finally {
|
|
1285
|
+
if (pendingFinalPublication === pending) cancelPendingFinalPublication();
|
|
1267
1286
|
}
|
|
1268
|
-
|
|
1287
|
+
if (!isSessionContextActive(ctx)) return;
|
|
1269
1288
|
agentWorkActive = false;
|
|
1270
1289
|
activityRuntime.onAgentSettled();
|
|
1271
1290
|
modelContextAvailabilityRuntime.reconcile();
|
package/lib/config.ts
CHANGED
package/lib/delivery.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type TelegramInlineKeyboardMarkup,
|
|
11
11
|
} from "./keyboard.ts";
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
withTelegramReplyParameters,
|
|
14
14
|
renderTelegramMessage,
|
|
15
15
|
} from "./replies.ts";
|
|
16
16
|
import {
|
|
@@ -724,23 +724,23 @@ export function createTelegramBridgeDeliveryRuntime(
|
|
|
724
724
|
},
|
|
725
725
|
async sendChunk(target, chunk, options) {
|
|
726
726
|
assertTransportActive();
|
|
727
|
-
const
|
|
728
|
-
target.chatId,
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
727
|
+
const sent = await withTelegramReplyParameters(
|
|
728
|
+
target.chatId, options.replyToMessageId, target,
|
|
729
|
+
(replyParameters) => {
|
|
730
|
+
const body = {
|
|
731
|
+
chat_id: target.chatId,
|
|
732
|
+
text: chunk.text,
|
|
733
|
+
...(chunk.parseMode === "html" ? { parse_mode: "HTML" as const } : {}),
|
|
734
|
+
...getTelegramTargetThreadParams(target),
|
|
735
|
+
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
736
|
+
...(options.replyMarkup ? { reply_markup: options.replyMarkup } : {}),
|
|
737
|
+
};
|
|
738
|
+
return deps.api.sendMessage(
|
|
739
|
+
target.threadId === undefined
|
|
740
|
+
? markTelegramBusAggregateDelivery(body)
|
|
741
|
+
: body,
|
|
742
|
+
);
|
|
743
|
+
},
|
|
744
744
|
);
|
|
745
745
|
assertTransportActive();
|
|
746
746
|
deps.recordOwnership({
|
package/lib/lifecycle.ts
CHANGED
|
@@ -37,9 +37,15 @@ export function setResetTransportReplyDedup(fn: () => void): void {
|
|
|
37
37
|
|
|
38
38
|
export function createAgentStartDedupHook(
|
|
39
39
|
inner: (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void>,
|
|
40
|
+
schedulePublication?: (task: () => Promise<void>) => void,
|
|
40
41
|
): (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void> {
|
|
41
42
|
return async (event, ctx) => {
|
|
42
|
-
|
|
43
|
+
const reset = resetTransportReplyDedupFn;
|
|
44
|
+
if (reset) {
|
|
45
|
+
// A new turn must not erase the anchor of a final still ahead in the FIFO.
|
|
46
|
+
if (schedulePublication) schedulePublication(async () => { reset(); });
|
|
47
|
+
else reset();
|
|
48
|
+
}
|
|
43
49
|
return inner(event, ctx);
|
|
44
50
|
};
|
|
45
51
|
}
|
|
@@ -450,8 +456,10 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
450
456
|
const setTimer = deps.setTimer ?? setTimeout;
|
|
451
457
|
const clearTimer = deps.clearTimer ?? clearTimeout;
|
|
452
458
|
let fallbackTimer: TelegramLifecycleTimer | undefined;
|
|
459
|
+
let observationGeneration = 0;
|
|
453
460
|
let typingStartedByObserver = false;
|
|
454
461
|
const clearFallbackTimer = (): void => {
|
|
462
|
+
observationGeneration += 1;
|
|
455
463
|
if (!fallbackTimer) return;
|
|
456
464
|
clearTimer(fallbackTimer);
|
|
457
465
|
fallbackTimer = undefined;
|
|
@@ -470,7 +478,10 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
470
478
|
!!deps.startTypingLoop && typingStartResult !== false;
|
|
471
479
|
deps.updateStatus(ctx);
|
|
472
480
|
clearFallbackTimer();
|
|
481
|
+
const admittedGeneration = observationGeneration;
|
|
473
482
|
fallbackTimer = setTimer(() => {
|
|
483
|
+
if (observationGeneration !== admittedGeneration) return;
|
|
484
|
+
observationGeneration += 1;
|
|
474
485
|
fallbackTimer = undefined;
|
|
475
486
|
if (deps.isContextActive && !deps.isContextActive(ctx)) return;
|
|
476
487
|
deps.setCompactionInProgress(false);
|
|
@@ -487,8 +498,8 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
487
498
|
unrefTelegramLifecycleTimer(fallbackTimer);
|
|
488
499
|
},
|
|
489
500
|
onSessionCompact: (_event, ctx) => {
|
|
490
|
-
clearFallbackTimer();
|
|
491
501
|
if (deps.isContextActive && !deps.isContextActive(ctx)) return;
|
|
502
|
+
clearFallbackTimer();
|
|
492
503
|
deps.setCompactionInProgress(false);
|
|
493
504
|
if (typingStartedByObserver) deps.stopTypingLoop?.();
|
|
494
505
|
typingStartedByObserver = false;
|
|
@@ -496,8 +507,8 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
496
507
|
requestDispatch();
|
|
497
508
|
},
|
|
498
509
|
onSessionCompactFailed: (_event, ctx) => {
|
|
499
|
-
clearFallbackTimer();
|
|
500
510
|
if (deps.isContextActive && !deps.isContextActive(ctx)) return;
|
|
511
|
+
clearFallbackTimer();
|
|
501
512
|
deps.setCompactionInProgress(false);
|
|
502
513
|
if (typingStartedByObserver) deps.stopTypingLoop?.();
|
|
503
514
|
typingStartedByObserver = false;
|