@llblab/pi-telegram 0.22.1 → 0.23.1
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 +24 -13
- package/BACKLOG.md +1 -51
- package/CHANGELOG.md +41 -19
- package/README.md +3 -1
- package/docs/README.md +1 -1
- package/docs/activity.md +8 -0
- package/docs/architecture.md +16 -15
- package/docs/locks.md +21 -15
- package/docs/multi-instance-bus.md +17 -16
- package/docs/outbound.md +16 -0
- package/docs/public-api.md +9 -4
- package/index.ts +76 -65
- package/lib/activity.ts +100 -3
- package/lib/bindings.ts +86 -15
- package/lib/bus-follower.ts +205 -17
- package/lib/bus-leader.ts +333 -244
- package/lib/bus.ts +82 -19
- package/lib/commands.ts +28 -4
- package/lib/config.ts +44 -33
- package/lib/media.ts +102 -1
- package/lib/menu-settings.ts +4 -1
- package/lib/outbound-attachments.ts +150 -1
- package/lib/outbound.ts +106 -1
- package/lib/polling.ts +5 -0
- package/lib/queue.ts +41 -48
- package/lib/routing.ts +88 -1
- package/lib/sync.ts +40 -3
- package/lib/telegram-api.ts +77 -13
- package/lib/text-groups.ts +183 -16
- package/lib/thread-reconciler.ts +69 -93
- package/lib/threads.ts +131 -66
- package/lib/turns.ts +102 -13
- package/lib/updates.ts +2 -0
- package/package.json +3 -2
package/index.ts
CHANGED
|
@@ -155,11 +155,6 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
155
155
|
contextStore: telegramSessionContextStore,
|
|
156
156
|
});
|
|
157
157
|
const activeTurnRuntime = Queue.createTelegramActiveTurnStore();
|
|
158
|
-
const proactivePushChatIdGetter =
|
|
159
|
-
Config.createTelegramProactivePushChatIdGetter({
|
|
160
|
-
getActiveTurnChatId: activeTurnRuntime.getChatId,
|
|
161
|
-
getAllowedUserId: configStore.getAllowedUserId,
|
|
162
|
-
});
|
|
163
158
|
const proactivePushTargetGetter =
|
|
164
159
|
Config.createTelegramProactivePushTargetGetter({
|
|
165
160
|
getActiveTurnTarget: activeTurnRuntime.getTarget,
|
|
@@ -171,6 +166,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
171
166
|
},
|
|
172
167
|
getAllowedUserId: configStore.getAllowedUserId,
|
|
173
168
|
});
|
|
169
|
+
const proactivePushChatIdGetter =
|
|
170
|
+
Config.createTelegramProactivePushChatIdGetter(proactivePushTargetGetter);
|
|
174
171
|
const buttonActionStore = Outbound.createTelegramButtonActionStore();
|
|
175
172
|
const pendingModelSwitchStore =
|
|
176
173
|
Model.createPendingModelSwitchStore<
|
|
@@ -220,14 +217,11 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
220
217
|
scheduleSnapshotPersist: runtimeDiagnostics.scheduleSnapshotPersist,
|
|
221
218
|
});
|
|
222
219
|
const recordThreadReconciliationPlan = threadReconciliationRuntime.recordPlan;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
await configStore.persist(nextConfig);
|
|
229
|
-
markTelegramConfigSyncChange("config-persist");
|
|
230
|
-
};
|
|
220
|
+
const persistTelegramConfigWithSync =
|
|
221
|
+
Sync.createTelegramConfigSyncPersister<Config.TelegramConfig>({
|
|
222
|
+
persist: configStore.persist,
|
|
223
|
+
markConfigChange: telegramSyncStateRuntime.markConfigChange,
|
|
224
|
+
});
|
|
231
225
|
const currentInstanceThreadRuntime =
|
|
232
226
|
Threads.createTelegramCurrentInstanceThreadRuntime({
|
|
233
227
|
instanceId: telegramInstanceId,
|
|
@@ -271,7 +265,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
271
265
|
getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
|
|
272
266
|
getFollowerSlot: telegramBusFollowerRegistrationState.getSlot,
|
|
273
267
|
getFollowerThreadName: telegramBusFollowerRegistrationState.getThreadName,
|
|
274
|
-
getCurrentIdentity:
|
|
268
|
+
getCurrentIdentity: currentInstanceThreadRuntime.getRestorationIdentity,
|
|
275
269
|
});
|
|
276
270
|
const statusRuntime = Status.createTelegramBridgeStatusRuntime<
|
|
277
271
|
Pi.ExtensionContext,
|
|
@@ -347,6 +341,9 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
347
341
|
},
|
|
348
342
|
getRegistrationGeneration:
|
|
349
343
|
telegramBusFollowerRegistrationState.getGeneration,
|
|
344
|
+
getForwardCommentBatchPosition:
|
|
345
|
+
textGroupRuntime.getPreparedForwardingPosition,
|
|
346
|
+
recordRuntimeEvent,
|
|
350
347
|
timeoutMs: 30_000,
|
|
351
348
|
});
|
|
352
349
|
const telegramApiRuntime = BusApi.createTelegramBusAwareApiRuntime({
|
|
@@ -445,8 +442,45 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
445
442
|
});
|
|
446
443
|
},
|
|
447
444
|
});
|
|
445
|
+
const { sendTextReply, sendMarkdownReply } =
|
|
446
|
+
Outbound.createTelegramOutboundTextReplyRuntime({
|
|
447
|
+
sendTextReply: replyRuntime.sendTextReply,
|
|
448
|
+
sendMarkdownReply: replyRuntime.sendMarkdownReply,
|
|
449
|
+
execCommand: CommandTemplates.execCommandTemplate,
|
|
450
|
+
getHandlers: configStore.getOutboundHandlers,
|
|
451
|
+
recordRuntimeEvent,
|
|
452
|
+
});
|
|
453
|
+
const assistantOutputBindingRuntime =
|
|
454
|
+
Bindings.createTelegramAssistantOutputBindingRuntime({
|
|
455
|
+
isEnabled: configControls.isProactivePushEnabled,
|
|
456
|
+
authority: {
|
|
457
|
+
getPreferredTarget: proactivePushTargetGetter,
|
|
458
|
+
getFallbackChatId: proactivePushChatIdGetter,
|
|
459
|
+
getTransportStamp: telegramTransportStampRuntime.getStamp,
|
|
460
|
+
isTransportStampActive: telegramTransportStampRuntime.isActive,
|
|
461
|
+
ownsDirect: lockRuntime.owns,
|
|
462
|
+
getDirectEpoch: lockRuntime.getOwnedLeaderEpoch,
|
|
463
|
+
isFollowerRegistered:
|
|
464
|
+
telegramBusFollowerRegistrationState.isRegistered,
|
|
465
|
+
getFollowerGeneration:
|
|
466
|
+
telegramBusFollowerRegistrationState.getGeneration,
|
|
467
|
+
},
|
|
468
|
+
sender: {
|
|
469
|
+
recordOwnership: messageOwnershipRuntime.recordLocal,
|
|
470
|
+
sendMessage,
|
|
471
|
+
sendRichMessage,
|
|
472
|
+
editMessage: editTelegramMessageText,
|
|
473
|
+
getAssistantRenderingMode: configControls.getAssistantRenderingMode,
|
|
474
|
+
execCommand: CommandTemplates.execCommandTemplate,
|
|
475
|
+
getHandlers: configStore.getOutboundHandlers,
|
|
476
|
+
recordRuntimeEvent,
|
|
477
|
+
},
|
|
478
|
+
recordRuntimeEvent,
|
|
479
|
+
});
|
|
480
|
+
const assistantOutputRuntime = assistantOutputBindingRuntime.runtime;
|
|
448
481
|
const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
|
|
449
482
|
generation: deliveryGenerationSeed,
|
|
483
|
+
observeEvent: assistantOutputBindingRuntime.observeEvent,
|
|
450
484
|
recordFailure(handlerId, event, error) {
|
|
451
485
|
recordRuntimeEvent("activity", error, {
|
|
452
486
|
handlerId,
|
|
@@ -455,14 +489,6 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
455
489
|
});
|
|
456
490
|
},
|
|
457
491
|
});
|
|
458
|
-
const { sendTextReply, sendMarkdownReply } =
|
|
459
|
-
Outbound.createTelegramOutboundTextReplyRuntime({
|
|
460
|
-
sendTextReply: replyRuntime.sendTextReply,
|
|
461
|
-
sendMarkdownReply: replyRuntime.sendMarkdownReply,
|
|
462
|
-
execCommand: CommandTemplates.execCommandTemplate,
|
|
463
|
-
getHandlers: configStore.getOutboundHandlers,
|
|
464
|
-
recordRuntimeEvent,
|
|
465
|
-
});
|
|
466
492
|
const dispatchNextQueuedTelegramTurn =
|
|
467
493
|
Queue.createTelegramQueueDispatchRuntime({
|
|
468
494
|
...telegramQueueStore,
|
|
@@ -622,9 +648,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
622
648
|
callApi: callTelegramApi,
|
|
623
649
|
isTopicProvisioningActive: telegramProvisioningActivity.isActive,
|
|
624
650
|
getCurrentLeaderEpoch,
|
|
625
|
-
getThreadReconciliationMachineState
|
|
626
|
-
return threadReconciliationRuntime.getState();
|
|
627
|
-
},
|
|
651
|
+
getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
|
|
628
652
|
recordThreadReconciliationPlan,
|
|
629
653
|
getSyncState: telegramSyncStateRuntime.getState,
|
|
630
654
|
setSyncState: telegramSyncStateRuntime.setState,
|
|
@@ -650,9 +674,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
650
674
|
recordMessageOwnership: messageOwnershipRuntime.recordRouted,
|
|
651
675
|
...inboundBusProjectionRuntime,
|
|
652
676
|
getCurrentLeaderEpoch,
|
|
653
|
-
getThreadReconciliationMachineState
|
|
654
|
-
return threadReconciliationRuntime.getState();
|
|
655
|
-
},
|
|
677
|
+
getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
|
|
656
678
|
recordThreadReconciliationPlan,
|
|
657
679
|
handleTelegramTopicLifecycleUpdate: topicLifecycleSync,
|
|
658
680
|
handleTelegramThreadTargetObserved(_target, ctx) {
|
|
@@ -744,13 +766,13 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
744
766
|
receiver: {
|
|
745
767
|
socketPath: getTelegramBusFollowerSocketPath,
|
|
746
768
|
instanceId: telegramInstanceId,
|
|
747
|
-
getContext
|
|
748
|
-
return telegramSessionContextStore.get();
|
|
749
|
-
},
|
|
769
|
+
getContext: telegramSessionContextStore.get,
|
|
750
770
|
getAuthSecret() {
|
|
751
771
|
return telegramActiveBusAuthSecret;
|
|
752
772
|
},
|
|
753
773
|
...forwardedRouteHandlers,
|
|
774
|
+
prepareForwardedMessage:
|
|
775
|
+
textGroupRuntime.prepareForwardedMessage,
|
|
754
776
|
recordRuntimeEvent,
|
|
755
777
|
},
|
|
756
778
|
targetReplacement: {
|
|
@@ -766,17 +788,13 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
766
788
|
},
|
|
767
789
|
recovery: {
|
|
768
790
|
registrationState: telegramBusFollowerRegistrationState,
|
|
769
|
-
getLeaderState
|
|
770
|
-
return lockRuntime.getState();
|
|
771
|
-
},
|
|
791
|
+
getLeaderState: lockRuntime.getState,
|
|
772
792
|
setLifecyclePhase(phase) {
|
|
773
793
|
telegramBusLifecycleOverridePhase = phase;
|
|
774
794
|
},
|
|
775
795
|
updateStatus,
|
|
776
796
|
promoteToLeader: promoteTelegramBusFollowerToLeader,
|
|
777
|
-
getActiveContext
|
|
778
|
-
return telegramSessionContextStore.get();
|
|
779
|
-
},
|
|
797
|
+
getActiveContext: telegramSessionContextStore.get,
|
|
780
798
|
recordRuntimeEvent,
|
|
781
799
|
},
|
|
782
800
|
registration: {
|
|
@@ -784,9 +802,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
784
802
|
getFollowerBusSocketPath: getTelegramBusFollowerSocketPath,
|
|
785
803
|
getLeaderSocketPath: getTelegramBusSocketPath,
|
|
786
804
|
registrationState: telegramBusFollowerRegistrationState,
|
|
787
|
-
isContextActive
|
|
788
|
-
return telegramSessionContextStore.isCurrent(ctx);
|
|
789
|
-
},
|
|
805
|
+
isContextActive: telegramSessionContextStore.isCurrent,
|
|
790
806
|
createRequestId: telegramBusFollowerClients.createRequestId,
|
|
791
807
|
getLeaderAuthSecret(owner) {
|
|
792
808
|
return owner.busSecret;
|
|
@@ -794,12 +810,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
794
810
|
setActiveAuthSecret(secret) {
|
|
795
811
|
telegramActiveBusAuthSecret = secret;
|
|
796
812
|
},
|
|
797
|
-
|
|
798
|
-
return undefined;
|
|
799
|
-
},
|
|
800
|
-
getProfileKey() {
|
|
801
|
-
return getTelegramManualFollowerProfileKey();
|
|
802
|
-
},
|
|
813
|
+
getProfileKey: getTelegramManualFollowerProfileKey,
|
|
803
814
|
recordRuntimeEvent,
|
|
804
815
|
},
|
|
805
816
|
});
|
|
@@ -812,6 +823,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
812
823
|
deleteWebhook,
|
|
813
824
|
getUpdates,
|
|
814
825
|
persistConfig: persistTelegramConfigWithSync,
|
|
826
|
+
prepareUpdateBatch: textGroupRuntime.prepareUpdateBatch,
|
|
815
827
|
handleUpdate: Updates.createTelegramUpdateHandle({
|
|
816
828
|
defaultHandle: inboundRouteRuntime.handleUpdate,
|
|
817
829
|
}),
|
|
@@ -819,17 +831,13 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
819
831
|
updateStatus,
|
|
820
832
|
recordRuntimeEvent,
|
|
821
833
|
});
|
|
822
|
-
const recoverStaleTelegramTopicApiError =
|
|
823
|
-
|
|
824
|
-
error: unknown,
|
|
825
|
-
) {
|
|
826
|
-
return Sync.recoverStaleTelegramTopicApiError(apiBody, error, {
|
|
834
|
+
const recoverStaleTelegramTopicApiError =
|
|
835
|
+
Sync.createTelegramStaleTopicApiErrorRecoveryRuntime({
|
|
827
836
|
topicTargetStore: threadStore,
|
|
828
837
|
getSyncState: telegramSyncStateRuntime.getState,
|
|
829
838
|
setSyncState: telegramSyncStateRuntime.setState,
|
|
830
839
|
recordEvent: recordRuntimeEvent,
|
|
831
840
|
});
|
|
832
|
-
};
|
|
833
841
|
const authorizeFollowerApiCall = Bus.createTelegramFollowerApiCallAuthorizer({
|
|
834
842
|
isMessageOwned: messageOwnershipRuntime.isOwnedByFollower,
|
|
835
843
|
});
|
|
@@ -851,9 +859,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
851
859
|
},
|
|
852
860
|
getAllowedUserId: configStore.getAllowedUserId,
|
|
853
861
|
instanceId: telegramInstanceId,
|
|
854
|
-
getCwd
|
|
855
|
-
return typeof ctx.cwd === "string" ? ctx.cwd : undefined;
|
|
856
|
-
},
|
|
862
|
+
getCwd: Pi.getExtensionContextCwd,
|
|
857
863
|
getTelegramProfile: getActiveTelegramThreadProfile,
|
|
858
864
|
shouldForceFreshUnnamed:
|
|
859
865
|
telegramThreadCapabilityState.shouldForceFreshLeaderThread,
|
|
@@ -865,9 +871,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
865
871
|
downloadFile: directTelegramApiRuntime.downloadFile,
|
|
866
872
|
recoverStaleTargetError: recoverStaleTelegramTopicApiError,
|
|
867
873
|
getCurrentLeaderEpoch,
|
|
868
|
-
getThreadReconciliationMachineState
|
|
869
|
-
return threadReconciliationRuntime.getState();
|
|
870
|
-
},
|
|
874
|
+
getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
|
|
871
875
|
recordThreadReconciliationPlan,
|
|
872
876
|
getSyncState: telegramSyncStateRuntime.getState,
|
|
873
877
|
setSyncState: telegramSyncStateRuntime.setState,
|
|
@@ -937,6 +941,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
937
941
|
getCurrentLeaderEpoch,
|
|
938
942
|
getLeaderTarget: telegramBusLeaderState.getTarget,
|
|
939
943
|
clearLeaderTarget: telegramBusLeaderState.clear,
|
|
944
|
+
disconnectFollowerThread:
|
|
945
|
+
telegramBusFollowerRegistration.disconnectFromLeader,
|
|
940
946
|
getSyncState: telegramSyncStateRuntime.getState,
|
|
941
947
|
setSyncState: telegramSyncStateRuntime.setState,
|
|
942
948
|
stopPolling: lockedPollingRuntime.stop,
|
|
@@ -970,6 +976,10 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
970
976
|
registrationRuntime: telegramBusFollowerRegistration,
|
|
971
977
|
instanceId: telegramInstanceId,
|
|
972
978
|
suspendPolling: lockedPollingRuntime.suspend,
|
|
979
|
+
isLeader: lockRuntime.owns,
|
|
980
|
+
getLeaderBinding: currentInstanceThreadRuntime.getRestorationIdentity,
|
|
981
|
+
getActiveContext: telegramSessionContextStore.get,
|
|
982
|
+
getActiveProfileName: getActiveTelegramThreadProfile,
|
|
973
983
|
getLeaderState: lockRuntime.getState,
|
|
974
984
|
updateStatus,
|
|
975
985
|
recordRuntimeEvent,
|
|
@@ -1000,6 +1010,11 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1000
1010
|
activeTurnRuntime,
|
|
1001
1011
|
lockedPollingRuntime,
|
|
1002
1012
|
stopPolling: disconnectTelegramAndDeleteCurrentThread,
|
|
1013
|
+
getDisconnectThreadName() {
|
|
1014
|
+
const record = findCurrentThreadRecord();
|
|
1015
|
+
if (!record?.target.threadId) return undefined;
|
|
1016
|
+
return record.threadName ?? "current Telegram thread";
|
|
1017
|
+
},
|
|
1003
1018
|
onTransportChanged: deliveryLifecycleRuntime.onSessionStart,
|
|
1004
1019
|
getStatusLines,
|
|
1005
1020
|
buttonActionStore,
|
|
@@ -1026,6 +1041,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1026
1041
|
onModelSelect: currentModelRuntime.onModelSelect,
|
|
1027
1042
|
},
|
|
1028
1043
|
activityRuntime,
|
|
1044
|
+
assistantOutputRuntime,
|
|
1029
1045
|
configStore,
|
|
1030
1046
|
abort,
|
|
1031
1047
|
typing,
|
|
@@ -1048,15 +1064,10 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1048
1064
|
deleteMessage: deleteTelegramMessage,
|
|
1049
1065
|
sendGuestReply,
|
|
1050
1066
|
finalizeMarkdownPreview,
|
|
1051
|
-
proactivePushChatIdGetter,
|
|
1052
1067
|
proactivePushTargetGetter,
|
|
1053
1068
|
isProactivePushEnabled: configControls.isProactivePushEnabled,
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
lockOwnershipGuard.ownsContext(ctx) ||
|
|
1057
|
-
telegramBusFollowerRegistrationState.isRegistered()
|
|
1058
|
-
);
|
|
1059
|
-
},
|
|
1069
|
+
getAssistantRenderingMode: configControls.getAssistantRenderingMode,
|
|
1070
|
+
recordMessageOwnership: messageOwnershipRuntime.recordLocal,
|
|
1060
1071
|
canSendAgentActivity(ctx) {
|
|
1061
1072
|
return (
|
|
1062
1073
|
lockOwnershipGuard.ownsContext(ctx) ||
|
package/lib/activity.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram activity lifecycle normalization and extension dispatch
|
|
3
3
|
* Zones: pi agent lifecycle, extension API, operational delivery
|
|
4
|
-
* Owns stable handler registration, evidence-based activity/source identity, assistant segment and reasoning normalization, executed-tool and compaction events, isolated non-blocking queues, shutdown fencing, diagnostics, and fresh delivery contexts; excludes Pi hook wiring,
|
|
4
|
+
* Owns stable handler registration, evidence-based activity/source identity, assistant segment and reasoning normalization, ordered public-output projection, executed-tool and compaction events, isolated non-blocking queues, shutdown fencing, diagnostics, and fresh delivery contexts; excludes Pi hook wiring, Telegram rendering implementation, raw transport clients, and consumer-extension behavior
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -91,6 +91,9 @@ export type TelegramActivityPayload =
|
|
|
91
91
|
export type TelegramActivityEvent = TelegramActivityEnvelope &
|
|
92
92
|
TelegramActivityPayload;
|
|
93
93
|
|
|
94
|
+
export type TelegramAssistantSegmentEvent = TelegramActivityEnvelope &
|
|
95
|
+
Extract<TelegramActivityPayload, { type: "assistant-segment" }>;
|
|
96
|
+
|
|
94
97
|
export interface TelegramActivityContext {
|
|
95
98
|
activityId: string;
|
|
96
99
|
sequence: number;
|
|
@@ -383,6 +386,7 @@ export function createTelegramActivityDispatcher(deps: {
|
|
|
383
386
|
/** @internal */
|
|
384
387
|
export function createTelegramActivityBridgeRuntime(deps: {
|
|
385
388
|
generation: string;
|
|
389
|
+
observeEvent?: (event: TelegramActivityEvent) => void;
|
|
386
390
|
recordFailure?: (
|
|
387
391
|
handlerId: string,
|
|
388
392
|
event: TelegramActivityEvent,
|
|
@@ -401,6 +405,11 @@ export function createTelegramActivityBridgeRuntime(deps: {
|
|
|
401
405
|
dispatcher: createTelegramActivityDispatcher({
|
|
402
406
|
recordFailure: deps.recordFailure,
|
|
403
407
|
}),
|
|
408
|
+
observeEvent: deps.observeEvent,
|
|
409
|
+
recordObserverFailure: deps.recordFailure
|
|
410
|
+
? (event, error) =>
|
|
411
|
+
deps.recordFailure!("@llblab/pi-telegram/proactive", event, error)
|
|
412
|
+
: undefined,
|
|
404
413
|
now: deps.now,
|
|
405
414
|
});
|
|
406
415
|
},
|
|
@@ -507,6 +516,11 @@ interface PendingAssistantSegment {
|
|
|
507
516
|
export function createTelegramActivityRuntime(deps: {
|
|
508
517
|
generation: string;
|
|
509
518
|
dispatcher: TelegramActivityDispatcher;
|
|
519
|
+
observeEvent?: (event: TelegramActivityEvent) => void;
|
|
520
|
+
recordObserverFailure?: (
|
|
521
|
+
event: TelegramActivityEvent,
|
|
522
|
+
error: unknown,
|
|
523
|
+
) => void;
|
|
510
524
|
now?: () => number;
|
|
511
525
|
}): TelegramActivityRuntime {
|
|
512
526
|
const now = deps.now ?? Date.now;
|
|
@@ -542,14 +556,20 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
542
556
|
const emit = (event: TelegramActivityPayload): void => {
|
|
543
557
|
const currentActivityId = ensureActivity();
|
|
544
558
|
sequence += 1;
|
|
545
|
-
|
|
559
|
+
const normalizedEvent = {
|
|
546
560
|
...event,
|
|
547
561
|
activityId: currentActivityId,
|
|
548
562
|
sequence,
|
|
549
563
|
source: activitySource,
|
|
550
564
|
...(activityTarget ? { target: activityTarget } : {}),
|
|
551
565
|
timestamp: now(),
|
|
552
|
-
} as TelegramActivityEvent
|
|
566
|
+
} as TelegramActivityEvent;
|
|
567
|
+
try {
|
|
568
|
+
deps.observeEvent?.(normalizedEvent);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
deps.recordObserverFailure?.(normalizedEvent, error);
|
|
571
|
+
}
|
|
572
|
+
deps.dispatcher.dispatch(normalizedEvent);
|
|
553
573
|
};
|
|
554
574
|
const flushPendingSegment = (
|
|
555
575
|
placement: "intermediate" | "final" | "terminal-partial",
|
|
@@ -681,3 +701,80 @@ export function createTelegramActivityRuntime(deps: {
|
|
|
681
701
|
},
|
|
682
702
|
};
|
|
683
703
|
}
|
|
704
|
+
|
|
705
|
+
// --- Public Assistant Output Projection ---
|
|
706
|
+
|
|
707
|
+
export interface TelegramAssistantOutputRuntime {
|
|
708
|
+
start: () => void;
|
|
709
|
+
accept: (event: TelegramAssistantSegmentEvent) => void;
|
|
710
|
+
waitForIdle: () => Promise<void>;
|
|
711
|
+
stop: () => void;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(deps: {
|
|
715
|
+
isEnabled: () => boolean;
|
|
716
|
+
captureAuthority?: () => TAuthority;
|
|
717
|
+
isAuthorityActive?: (authority: TAuthority) => boolean;
|
|
718
|
+
canDeliver: (event: TelegramAssistantSegmentEvent) => boolean;
|
|
719
|
+
send: (
|
|
720
|
+
event: TelegramAssistantSegmentEvent,
|
|
721
|
+
authority: TAuthority,
|
|
722
|
+
isAuthorityActive: () => boolean,
|
|
723
|
+
) => Promise<void>;
|
|
724
|
+
recordFailure?: (
|
|
725
|
+
event: TelegramAssistantSegmentEvent,
|
|
726
|
+
error: unknown,
|
|
727
|
+
) => void;
|
|
728
|
+
}): TelegramAssistantOutputRuntime {
|
|
729
|
+
let generation = 0;
|
|
730
|
+
let running = false;
|
|
731
|
+
let tail: Promise<void> = Promise.resolve();
|
|
732
|
+
const admitted = new Set<string>();
|
|
733
|
+
const isEligibleSource = (
|
|
734
|
+
source: TelegramAssistantSegmentEvent["source"],
|
|
735
|
+
): boolean => source === "local" || source === "autonomous";
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
start() {
|
|
739
|
+
generation += 1;
|
|
740
|
+
running = true;
|
|
741
|
+
admitted.clear();
|
|
742
|
+
tail = Promise.resolve();
|
|
743
|
+
},
|
|
744
|
+
accept(event) {
|
|
745
|
+
if (!running || !deps.isEnabled()) return;
|
|
746
|
+
if (!isEligibleSource(event.source) || !event.text.trim()) return;
|
|
747
|
+
const key = `${event.activityId}:${event.sequence}`;
|
|
748
|
+
if (admitted.has(key)) return;
|
|
749
|
+
admitted.add(key);
|
|
750
|
+
const admittedGeneration = generation;
|
|
751
|
+
const admittedAuthority = deps.captureAuthority?.();
|
|
752
|
+
tail = tail.then(async () => {
|
|
753
|
+
const isAdmittedAuthorityActive = () =>
|
|
754
|
+
running &&
|
|
755
|
+
generation === admittedGeneration &&
|
|
756
|
+
deps.isEnabled() &&
|
|
757
|
+
(deps.isAuthorityActive === undefined ||
|
|
758
|
+
deps.isAuthorityActive(admittedAuthority as TAuthority));
|
|
759
|
+
if (!isAdmittedAuthorityActive() || !deps.canDeliver(event)) return;
|
|
760
|
+
try {
|
|
761
|
+
await deps.send(
|
|
762
|
+
event,
|
|
763
|
+
admittedAuthority as TAuthority,
|
|
764
|
+
isAdmittedAuthorityActive,
|
|
765
|
+
);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
deps.recordFailure?.(event, error);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
},
|
|
771
|
+
waitForIdle() {
|
|
772
|
+
return tail;
|
|
773
|
+
},
|
|
774
|
+
stop() {
|
|
775
|
+
generation += 1;
|
|
776
|
+
running = false;
|
|
777
|
+
admitted.clear();
|
|
778
|
+
},
|
|
779
|
+
};
|
|
780
|
+
}
|
package/lib/bindings.ts
CHANGED
|
@@ -19,6 +19,7 @@ import * as Preview from "./preview.ts";
|
|
|
19
19
|
import * as Prompts from "./prompts.ts";
|
|
20
20
|
import * as Queue from "./queue.ts";
|
|
21
21
|
import * as Replies from "./replies.ts";
|
|
22
|
+
import * as Routing from "./routing.ts";
|
|
22
23
|
import * as Runtime from "./runtime.ts";
|
|
23
24
|
import * as Setup from "./setup.ts";
|
|
24
25
|
import * as Status from "./status.ts";
|
|
@@ -35,6 +36,60 @@ type TelegramRuntimeEventRecorder = (
|
|
|
35
36
|
type TelegramBridgeStatusUpdater =
|
|
36
37
|
Status.TelegramStatusRuntime<Pi.ExtensionContext>["updateStatus"];
|
|
37
38
|
|
|
39
|
+
export interface TelegramAssistantOutputBindingRuntime<
|
|
40
|
+
TTransportStamp,
|
|
41
|
+
> {
|
|
42
|
+
runtime: Activity.TelegramAssistantOutputRuntime;
|
|
43
|
+
observeEvent: (event: Activity.TelegramActivityEvent) => void;
|
|
44
|
+
authority: Routing.TelegramAssistantOutputAuthorityRuntime<TTransportStamp>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createTelegramAssistantOutputBindingRuntime<
|
|
48
|
+
TTransportStamp,
|
|
49
|
+
>(deps: {
|
|
50
|
+
isEnabled: () => boolean;
|
|
51
|
+
authority: {
|
|
52
|
+
getPreferredTarget: () => OutboundAttachments.TelegramQueuedOutboundAttachmentTurnView["target"] | undefined;
|
|
53
|
+
getFallbackChatId: () => number | undefined;
|
|
54
|
+
getTransportStamp: () => TTransportStamp;
|
|
55
|
+
isTransportStampActive: (stamp: TTransportStamp) => boolean;
|
|
56
|
+
ownsDirect: () => boolean;
|
|
57
|
+
getDirectEpoch: () => number | string | undefined;
|
|
58
|
+
isFollowerRegistered: () => boolean;
|
|
59
|
+
getFollowerGeneration: () => string | undefined;
|
|
60
|
+
};
|
|
61
|
+
sender: Parameters<
|
|
62
|
+
typeof OutboundHandlers.createTelegramAssistantOutputSender<TTransportStamp>
|
|
63
|
+
>[0];
|
|
64
|
+
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
65
|
+
}): TelegramAssistantOutputBindingRuntime<TTransportStamp> {
|
|
66
|
+
const authority =
|
|
67
|
+
Routing.createTelegramAssistantOutputAuthorityRuntime(deps.authority);
|
|
68
|
+
const send =
|
|
69
|
+
OutboundHandlers.createTelegramAssistantOutputSender<TTransportStamp>(
|
|
70
|
+
deps.sender,
|
|
71
|
+
);
|
|
72
|
+
const runtime = Activity.createTelegramAssistantOutputRuntime({
|
|
73
|
+
isEnabled: deps.isEnabled,
|
|
74
|
+
...authority,
|
|
75
|
+
send,
|
|
76
|
+
recordFailure(event, error) {
|
|
77
|
+
deps.recordRuntimeEvent("proactive-push", error, {
|
|
78
|
+
activityId: event.activityId,
|
|
79
|
+
sequence: event.sequence,
|
|
80
|
+
placement: event.placement,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
runtime,
|
|
86
|
+
authority,
|
|
87
|
+
observeEvent(event) {
|
|
88
|
+
if (event.type === "assistant-segment") runtime.accept(event);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
38
93
|
interface TelegramCommandsAndToolsBindingDeps {
|
|
39
94
|
pi: Pi.ExtensionAPI;
|
|
40
95
|
configStore: Config.TelegramConfigStore;
|
|
@@ -43,6 +98,7 @@ interface TelegramCommandsAndToolsBindingDeps {
|
|
|
43
98
|
activeTurnRuntime: Queue.TelegramActiveTurnStore<Queue.PendingTelegramTurn>;
|
|
44
99
|
lockedPollingRuntime: Locks.TelegramLockedPollingRuntime<Pi.ExtensionContext>;
|
|
45
100
|
stopPolling?: () => Promise<void | string>;
|
|
101
|
+
getDisconnectThreadName?: () => string | undefined;
|
|
46
102
|
onTransportChanged?: () => Promise<void> | void;
|
|
47
103
|
getStatusLines: (
|
|
48
104
|
options?: Status.TelegramBridgeStatusLineOptions,
|
|
@@ -70,6 +126,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
70
126
|
activeTurnRuntime,
|
|
71
127
|
lockedPollingRuntime,
|
|
72
128
|
stopPolling,
|
|
129
|
+
getDisconnectThreadName,
|
|
73
130
|
onTransportChanged,
|
|
74
131
|
getStatusLines,
|
|
75
132
|
buttonActionStore,
|
|
@@ -186,6 +243,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
186
243
|
hasBotToken: configStore.hasBotToken,
|
|
187
244
|
startPolling: lockedPollingRuntime.start,
|
|
188
245
|
stopPolling: stopPolling ?? lockedPollingRuntime.stop,
|
|
246
|
+
getDisconnectThreadName,
|
|
189
247
|
updateStatus,
|
|
190
248
|
getProfileNames: () =>
|
|
191
249
|
Config.getTelegramProfileNames(configStore.getStoredConfig()),
|
|
@@ -217,6 +275,10 @@ export function registerTelegramCommandsAndTools({
|
|
|
217
275
|
interface TelegramLifecycleBindingDeps {
|
|
218
276
|
pi: Pi.ExtensionAPI;
|
|
219
277
|
activityRuntime: Activity.TelegramActivityRuntime;
|
|
278
|
+
assistantOutputRuntime: Pick<
|
|
279
|
+
Activity.TelegramAssistantOutputRuntime,
|
|
280
|
+
"start" | "stop"
|
|
281
|
+
>;
|
|
220
282
|
sessionLifecycleRuntime: Pick<
|
|
221
283
|
Lifecycle.TelegramLifecycleRegistrationDeps,
|
|
222
284
|
"onSessionStart" | "onSessionShutdown" | "onModelSelect"
|
|
@@ -282,10 +344,14 @@ interface TelegramLifecycleBindingDeps {
|
|
|
282
344
|
Pi.AgentEndEvent["messages"][number],
|
|
283
345
|
Keyboard.TelegramInlineKeyboardMarkup
|
|
284
346
|
>["finalizeMarkdownPreview"];
|
|
285
|
-
proactivePushChatIdGetter: () => number | undefined;
|
|
286
347
|
proactivePushTargetGetter: () => Queue.TelegramQueueTarget | undefined;
|
|
287
348
|
isProactivePushEnabled: () => boolean;
|
|
288
|
-
|
|
349
|
+
getAssistantRenderingMode: () => "rich" | "html";
|
|
350
|
+
recordMessageOwnership?: (input: {
|
|
351
|
+
chatId: number;
|
|
352
|
+
messageId: number;
|
|
353
|
+
target?: Queue.TelegramQueueTarget;
|
|
354
|
+
}) => void;
|
|
289
355
|
canSendAgentActivity: (ctx: Pi.ExtensionContext) => boolean;
|
|
290
356
|
isSessionContextActive: (ctx: Pi.ExtensionContext) => boolean;
|
|
291
357
|
isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
|
|
@@ -296,6 +362,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
296
362
|
export function registerTelegramLifecycleRuntimeHooks({
|
|
297
363
|
pi,
|
|
298
364
|
activityRuntime,
|
|
365
|
+
assistantOutputRuntime,
|
|
299
366
|
sessionLifecycleRuntime,
|
|
300
367
|
configStore,
|
|
301
368
|
abort,
|
|
@@ -319,10 +386,10 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
319
386
|
deleteMessage,
|
|
320
387
|
sendGuestReply,
|
|
321
388
|
finalizeMarkdownPreview,
|
|
322
|
-
proactivePushChatIdGetter,
|
|
323
389
|
proactivePushTargetGetter,
|
|
324
390
|
isProactivePushEnabled,
|
|
325
|
-
|
|
391
|
+
getAssistantRenderingMode,
|
|
392
|
+
recordMessageOwnership,
|
|
326
393
|
canSendAgentActivity,
|
|
327
394
|
isSessionContextActive = () => true,
|
|
328
395
|
isTurnTransportActive,
|
|
@@ -343,13 +410,20 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
343
410
|
sendTextReply,
|
|
344
411
|
recordRuntimeEvent,
|
|
345
412
|
});
|
|
413
|
+
const richAttachmentSender =
|
|
414
|
+
OutboundAttachments.createTelegramRichOutboundAttachmentSender({
|
|
415
|
+
sendMultipart: callMultipart,
|
|
416
|
+
getRenderingMode: getAssistantRenderingMode,
|
|
417
|
+
recordOwnership: recordMessageOwnership,
|
|
418
|
+
recordRuntimeEvent,
|
|
419
|
+
});
|
|
346
420
|
const sendGuestAttachment = async (
|
|
347
421
|
turn: Queue.PendingTelegramTurn,
|
|
348
422
|
attachment: Queue.QueuedAttachment,
|
|
349
423
|
caption?: string,
|
|
350
424
|
): Promise<void> => {
|
|
351
425
|
const stagingTarget = proactivePushTargetGetter();
|
|
352
|
-
const stagingChatId = stagingTarget?.chatId
|
|
426
|
+
const stagingChatId = stagingTarget?.chatId;
|
|
353
427
|
if (stagingChatId === undefined) {
|
|
354
428
|
throw new Error(
|
|
355
429
|
"Guest attachment staging requires a paired Telegram chat",
|
|
@@ -394,7 +468,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
394
468
|
caption?: string,
|
|
395
469
|
): Promise<void> => {
|
|
396
470
|
const stagingTarget = proactivePushTargetGetter();
|
|
397
|
-
const stagingChatId = stagingTarget?.chatId
|
|
471
|
+
const stagingChatId = stagingTarget?.chatId;
|
|
398
472
|
if (stagingChatId === undefined) {
|
|
399
473
|
throw new Error("Guest voice staging requires a paired Telegram chat");
|
|
400
474
|
}
|
|
@@ -499,16 +573,13 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
499
573
|
sendMarkdownReply,
|
|
500
574
|
sendTextReply,
|
|
501
575
|
sendQueuedAttachments: queuedAttachmentSender,
|
|
576
|
+
sendRichAttachmentReply: richAttachmentSender,
|
|
502
577
|
answerGuestQuery,
|
|
503
578
|
sendGuestReply,
|
|
504
579
|
sendGuestAttachment,
|
|
505
580
|
sendGuestVoiceReply,
|
|
506
581
|
planOutboundReply: outboundReplyPlanner,
|
|
507
582
|
sendOutboundReplyArtifacts: outboundReplyArtifactSender,
|
|
508
|
-
getDefaultChatId: proactivePushChatIdGetter,
|
|
509
|
-
getDefaultTarget: proactivePushTargetGetter,
|
|
510
|
-
isProactivePushEnabled,
|
|
511
|
-
canSendProactivePush,
|
|
512
583
|
recordRuntimeEvent,
|
|
513
584
|
getActiveToolExecutions: lifecycle.getActiveToolExecutions,
|
|
514
585
|
setActiveToolExecutions: lifecycle.setActiveToolExecutions,
|
|
@@ -522,11 +593,9 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
522
593
|
if (!canSendAgentActivity(ctx)) return false;
|
|
523
594
|
const turn = activeTurnRuntime.get();
|
|
524
595
|
const target = turn?.target ?? proactivePushTargetGetter();
|
|
525
|
-
promptDispatchRuntime.startTypingLoop(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
{ target },
|
|
529
|
-
);
|
|
596
|
+
promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId ?? target?.chatId, {
|
|
597
|
+
target,
|
|
598
|
+
});
|
|
530
599
|
return true;
|
|
531
600
|
};
|
|
532
601
|
const startActiveTurnTypingLoop = (ctx: Pi.ExtensionContext): void => {
|
|
@@ -565,12 +634,14 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
565
634
|
},
|
|
566
635
|
async onSessionStart(event, ctx) {
|
|
567
636
|
previewRuntime.invalidate();
|
|
637
|
+
assistantOutputRuntime.start();
|
|
568
638
|
activityRuntime.onSessionStart?.();
|
|
569
639
|
await sessionLifecycleRuntime.onSessionStart(event, ctx);
|
|
570
640
|
},
|
|
571
641
|
async onSessionShutdown(event, ctx) {
|
|
572
642
|
if (!isSessionContextActive(ctx)) return;
|
|
573
643
|
activityRuntime.onSessionShutdown();
|
|
644
|
+
assistantOutputRuntime.stop();
|
|
574
645
|
compactionObserver.onSessionShutdown();
|
|
575
646
|
await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
|
|
576
647
|
},
|