@llblab/pi-kit 0.5.1 → 0.6.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/CHANGELOG.md +8 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +6 -6
- package/node_modules/@llblab/pi-grow-loop/index.ts +6 -3
- package/node_modules/@llblab/pi-grow-loop/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +20 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +4 -0
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +27 -5
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -0
- package/node_modules/@llblab/pi-telegram/index.ts +24 -19
- package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +43 -25
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +79 -6
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +110 -91
- package/node_modules/@llblab/pi-telegram/lib/config.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +18 -18
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +14 -3
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +41 -37
- package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +39 -42
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +28 -17
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +134 -73
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +113 -72
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +46 -38
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +192 -58
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +36 -3
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +27 -35
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/skills/abcd-context/AGENTS.md +1 -0
- package/node_modules/@llblab/skills/abcd-context/CHANGELOG.md +6 -2
- package/node_modules/@llblab/skills/abcd-context/SKILL.md +1 -1
- package/node_modules/@llblab/skills/abcd-context/docs/validation-design.md +11 -5
- package/node_modules/@llblab/skills/abcd-context/scripts/_self-test.mjs +61 -0
- package/node_modules/@llblab/skills/abcd-context/scripts/validate-context.mjs +67 -0
- package/node_modules/@llblab/skills/package.json +1 -1
- package/node_modules/@llblab/skills/release-flow/SKILL.md +2 -4
- package/package.json +4 -4
|
@@ -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();
|
|
@@ -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({
|
|
@@ -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;
|
|
@@ -184,8 +184,8 @@ export function buildDraftPreviewsSettingsText(enabled: boolean): string {
|
|
|
184
184
|
"",
|
|
185
185
|
"Show live answer drafts while the model is answering.",
|
|
186
186
|
"",
|
|
187
|
-
"<code>-</code> <code>on</code
|
|
188
|
-
"<code>-</code> <code>off</code
|
|
187
|
+
"<code>-</code> <code>on</code> (default): stream safe Telegram Rich Draft frames before the final answer.",
|
|
188
|
+
"<code>-</code> <code>off</code>: show native active status, then send one final answer.",
|
|
189
189
|
].join("\n");
|
|
190
190
|
}
|
|
191
191
|
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
TELEGRAM_MESSAGE_PROMPT_SNIPPET,
|
|
22
22
|
} from "./prompts.ts";
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
withTelegramReplyParameters,
|
|
25
25
|
normalizeTelegramNativeMarkdown,
|
|
26
26
|
} from "./replies.ts";
|
|
27
27
|
import {
|
|
@@ -189,18 +189,10 @@ export function planTelegramRichOutboundAttachment(options: {
|
|
|
189
189
|
],
|
|
190
190
|
skip_entity_detection: true,
|
|
191
191
|
};
|
|
192
|
-
const replyParameters =
|
|
193
|
-
options.turn.replyToMessageId > 0
|
|
194
|
-
? JSON.stringify({
|
|
195
|
-
message_id: options.turn.replyToMessageId,
|
|
196
|
-
allow_sending_without_reply: true,
|
|
197
|
-
})
|
|
198
|
-
: undefined;
|
|
199
192
|
return {
|
|
200
193
|
method: "sendRichMessage",
|
|
201
194
|
fields: {
|
|
202
195
|
chat_id: String(options.turn.chatId),
|
|
203
|
-
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
204
196
|
...getTelegramMultipartTargetFields(options.turn.target),
|
|
205
197
|
rich_message: JSON.stringify(richMessage),
|
|
206
198
|
...(options.replyMarkup
|
|
@@ -219,8 +211,9 @@ export function createTelegramRichOutboundAttachmentSender(
|
|
|
219
211
|
return async (
|
|
220
212
|
turn: TelegramQueuedOutboundAttachmentTurnView,
|
|
221
213
|
markdown: string,
|
|
222
|
-
options?: { replyMarkup?: unknown },
|
|
214
|
+
options?: { replyMarkup?: unknown; isDeliveryActive?: () => boolean },
|
|
223
215
|
): Promise<boolean> => {
|
|
216
|
+
if (options?.isDeliveryActive?.() === false) return false;
|
|
224
217
|
const plan = planTelegramRichOutboundAttachment({
|
|
225
218
|
turn,
|
|
226
219
|
markdown,
|
|
@@ -229,12 +222,15 @@ export function createTelegramRichOutboundAttachmentSender(
|
|
|
229
222
|
});
|
|
230
223
|
if (!plan) return false;
|
|
231
224
|
try {
|
|
232
|
-
const result = await
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
225
|
+
const result = await withTelegramReplyParameters(
|
|
226
|
+
turn.chatId, turn.replyToMessageId, turn.target,
|
|
227
|
+
(replyParameters) => deps.sendMultipart(
|
|
228
|
+
plan.method,
|
|
229
|
+
{ ...plan.fields, ...(replyParameters ? { reply_parameters: JSON.stringify(replyParameters) } : {}) },
|
|
230
|
+
plan.fileField,
|
|
231
|
+
plan.filePath,
|
|
232
|
+
plan.fileName,
|
|
233
|
+
),
|
|
238
234
|
);
|
|
239
235
|
const messageId =
|
|
240
236
|
result && typeof result === "object" &&
|
|
@@ -246,11 +242,13 @@ export function createTelegramRichOutboundAttachmentSender(
|
|
|
246
242
|
new Error("Successful Rich media upload omitted message_id."),
|
|
247
243
|
);
|
|
248
244
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
245
|
+
if (options?.isDeliveryActive?.() !== false) {
|
|
246
|
+
deps.recordOwnership?.({
|
|
247
|
+
chatId: turn.chatId,
|
|
248
|
+
messageId,
|
|
249
|
+
target: turn.target,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
254
252
|
return true;
|
|
255
253
|
} catch (error) {
|
|
256
254
|
if (isTelegramRichAttachmentCommitUnknownError(error)) throw error;
|
|
@@ -580,6 +578,7 @@ export interface TelegramQueuedOutboundAttachmentDeliveryDeps {
|
|
|
580
578
|
) => void;
|
|
581
579
|
statPath?: (path: string) => Promise<{ size: number }>;
|
|
582
580
|
maxAttachmentSizeBytes?: number;
|
|
581
|
+
isDeliveryActive?: () => boolean;
|
|
583
582
|
}
|
|
584
583
|
|
|
585
584
|
export async function queueTelegramOutboundAttachments(options: {
|
|
@@ -925,9 +924,13 @@ export async function sendTelegramOutboundFiles(options: {
|
|
|
925
924
|
export function createTelegramQueuedOutboundAttachmentSender(
|
|
926
925
|
deps: TelegramQueuedOutboundAttachmentDeliveryDeps,
|
|
927
926
|
) {
|
|
928
|
-
return async (
|
|
927
|
+
return async (
|
|
928
|
+
turn: TelegramQueuedOutboundAttachmentTurnView,
|
|
929
|
+
options?: { isDeliveryActive?: () => boolean },
|
|
930
|
+
): Promise<void> => {
|
|
929
931
|
await sendQueuedTelegramOutboundAttachments(turn, {
|
|
930
932
|
...deps,
|
|
933
|
+
isDeliveryActive: () => deps.isDeliveryActive?.() !== false && options?.isDeliveryActive?.() !== false,
|
|
931
934
|
maxAttachmentSizeBytes:
|
|
932
935
|
deps.maxAttachmentSizeBytes ?? TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES,
|
|
933
936
|
});
|
|
@@ -939,9 +942,11 @@ export async function sendQueuedTelegramOutboundAttachments(
|
|
|
939
942
|
deps: TelegramQueuedOutboundAttachmentDeliveryDeps,
|
|
940
943
|
): Promise<void> {
|
|
941
944
|
for (const attachment of turn.queuedAttachments) {
|
|
945
|
+
if (deps.isDeliveryActive?.() === false) return;
|
|
942
946
|
try {
|
|
943
947
|
if (deps.maxAttachmentSizeBytes !== undefined) {
|
|
944
948
|
const stats = await (deps.statPath ?? stat)(attachment.path);
|
|
949
|
+
if (deps.isDeliveryActive?.() === false) return;
|
|
945
950
|
if (stats.size > deps.maxAttachmentSizeBytes) {
|
|
946
951
|
throw new Error(
|
|
947
952
|
formatTelegramOutboundAttachmentSizeLimitError(
|
|
@@ -954,23 +959,22 @@ export async function sendQueuedTelegramOutboundAttachments(
|
|
|
954
959
|
const isPhoto = isTelegramOutboundPhotoAttachmentPath(attachment.path);
|
|
955
960
|
const method = isPhoto ? "sendPhoto" : "sendDocument";
|
|
956
961
|
const fieldName = isPhoto ? "photo" : "document";
|
|
957
|
-
|
|
958
|
-
turn.chatId,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
attachment.path,
|
|
971
|
-
attachment.fileName,
|
|
962
|
+
await withTelegramReplyParameters(
|
|
963
|
+
turn.chatId, turn.replyToMessageId, turn.target,
|
|
964
|
+
(replyParameters) => deps.sendMultipart(
|
|
965
|
+
method,
|
|
966
|
+
{
|
|
967
|
+
chat_id: String(turn.chatId),
|
|
968
|
+
...(replyParameters ? { reply_parameters: JSON.stringify(replyParameters) } : {}),
|
|
969
|
+
...getTelegramMultipartTargetFields(turn.target),
|
|
970
|
+
},
|
|
971
|
+
fieldName,
|
|
972
|
+
attachment.path,
|
|
973
|
+
attachment.fileName,
|
|
974
|
+
),
|
|
972
975
|
);
|
|
973
976
|
} catch (error) {
|
|
977
|
+
if (deps.isDeliveryActive?.() === false) return;
|
|
974
978
|
const message = error instanceof Error ? error.message : String(error);
|
|
975
979
|
deps.recordRuntimeEvent?.("attachment", error, {
|
|
976
980
|
fileName: attachment.fileName,
|