@llblab/pi-telegram 0.27.11 → 0.28.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/index.ts CHANGED
@@ -4,9 +4,7 @@
4
4
  * Keeps the runtime wiring in one place while delegating reusable domain logic to /lib modules
5
5
  */
6
6
 
7
- import * as Activity from "./lib/activity.ts";
8
7
  import * as AgentMessages from "./lib/agent-messages.ts";
9
- import * as ActivityVerbosity from "./lib/activity-verbosity.ts";
10
8
  import * as Bindings from "./lib/bindings.ts";
11
9
  import * as BusApi from "./lib/bus-api.ts";
12
10
  import * as BusFollower from "./lib/bus-follower.ts";
@@ -18,6 +16,7 @@ import * as Commands from "./lib/commands.ts";
18
16
  import * as Config from "./lib/config.ts";
19
17
  import * as Delivery from "./lib/delivery.ts";
20
18
  import * as Inbound from "./lib/inbound.ts";
19
+ import * as Journal from "./lib/journal.ts";
21
20
  import * as Lifecycle from "./lib/lifecycle.ts";
22
21
  import * as Locks from "./lib/locks.ts";
23
22
  import * as Logs from "./lib/logs.ts";
@@ -32,8 +31,8 @@ import * as Paths from "./lib/paths.ts";
32
31
  import * as Pi from "./lib/pi.ts";
33
32
  import * as Polling from "./lib/polling.ts";
34
33
  import * as Preview from "./lib/preview.ts";
35
- import * as Prompts from "./lib/prompts.ts";
36
34
  import * as PromptTemplates from "./lib/prompt-templates.ts";
35
+ import * as Prompts from "./lib/prompts.ts";
37
36
  import * as Queue from "./lib/queue.ts";
38
37
  import * as Recovery from "./lib/recovery.ts";
39
38
  import * as Replies from "./lib/replies.ts";
@@ -52,6 +51,12 @@ import * as Voice from "./lib/voice.ts";
52
51
 
53
52
  type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
54
53
 
54
+ const telegramBusProtocolIdentity =
55
+ Bus.createTelegramCurrentBusProtocolIdentity([
56
+ Bus.TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
57
+ Bus.TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
58
+ ]);
59
+
55
60
  // --- Extension Runtime ---
56
61
 
57
62
  export default function (pi: Pi.ExtensionAPI) {
@@ -66,37 +71,39 @@ export default function (pi: Pi.ExtensionAPI) {
66
71
  setThinkingLevel,
67
72
  } = piRuntime;
68
73
  const bridgeRuntime = Runtime.createTelegramBridgeRuntime();
69
- const getActiveTelegramThreadProfile = function (): string | undefined {
70
- return configStore.getActiveProfileName();
71
- };
74
+ const runtimeDiagnostics =
75
+ Logs.createTelegramRuntimeDiagnosticsRuntime<Pi.ExtensionContext>();
76
+ const runtimeEvents = runtimeDiagnostics.events;
77
+ const recordRuntimeEvent = runtimeDiagnostics.recordRuntimeEvent;
78
+ const configStore = Config.createTelegramConfigStore({ recordRuntimeEvent });
72
79
  const busProcessRuntime = Bus.createCurrentTelegramBusProcessRuntime({
73
- getActiveProfileName: getActiveTelegramThreadProfile,
80
+ getActiveProfileName: configStore.getActiveProfileName,
74
81
  });
75
82
  const {
76
83
  instanceId: telegramInstanceId,
84
+ processId: telegramProcessId,
85
+ processBirthId: telegramQueueProcessBirthId,
77
86
  manualFollowerOwnerId: telegramManualFollowerOwnerId,
78
87
  getLeaderSocketPath: getTelegramBusSocketPath,
79
88
  getFollowerSocketPath: getTelegramBusFollowerSocketPath,
80
89
  } = busProcessRuntime;
90
+ const getTelegramBotId = Config.createTelegramConfigBotIdGetter(configStore);
91
+ const getTelegramActiveProfileKey =
92
+ Config.createTelegramActiveProfileKeyGetter(configStore);
81
93
  const getTelegramManualFollowerProfileKey =
82
94
  BusFollower.createTelegramManualFollowerProfileKeyResolver({
83
- getActiveProfileName: getActiveTelegramThreadProfile,
95
+ getActiveProfileName: configStore.getActiveProfileName,
84
96
  manualFollowerOwnerId: telegramManualFollowerOwnerId,
85
97
  });
86
98
  const telegramBusAuthSecret = Bus.createTelegramBusAuthSecret();
87
99
  const telegramBusFollowerControlState =
88
100
  BusFollower.createTelegramBusFollowerControlState();
89
101
  const telegramBusFollowerRegistry = Bus.createTelegramBusFollowerRegistry();
90
- // Late-bound composition port breaks the follower-availability callback cycle;
91
- // the mutable availability policy itself remains owned by prompts.
92
- let modelContextAvailabilityRuntime:
93
- | Prompts.TelegramModelContextAvailabilityRuntime
94
- | undefined;
102
+ const modelContextAvailabilityBinding =
103
+ Prompts.createTelegramModelContextAvailabilityBinding();
95
104
  const telegramBusFollowerRegistrationState =
96
105
  BusFollower.createTelegramBusFollowerRegistrationState({
97
- onAvailabilityChanged() {
98
- modelContextAvailabilityRuntime?.reconcile();
99
- },
106
+ onAvailabilityChanged: modelContextAvailabilityBinding.reconcile,
100
107
  });
101
108
  const telegramBusLeaderState =
102
109
  Threads.createTelegramLeaderThreadStateRuntime();
@@ -107,18 +114,49 @@ export default function (pi: Pi.ExtensionAPI) {
107
114
  const messageOwnershipRuntime =
108
115
  Ownership.createTelegramBusMessageOwnershipRuntime({
109
116
  instanceId: telegramInstanceId,
110
- getProfileKey() {
111
- return getActiveTelegramThreadProfile() ?? Config.TELEGRAM_DEFAULT_PROFILE_NAME;
112
- },
117
+ getProfileKey: getTelegramActiveProfileKey,
113
118
  listFollowers: telegramBusFollowerRegistry.list,
114
119
  });
115
120
  const { abort, lifecycle, queue, setup, typing } = bridgeRuntime;
116
- const runtimeDiagnostics =
117
- Logs.createTelegramRuntimeDiagnosticsRuntime<Pi.ExtensionContext>();
118
- const runtimeEvents = runtimeDiagnostics.events;
119
- const recordRuntimeEvent = runtimeDiagnostics.recordRuntimeEvent;
120
- const configStore = Config.createTelegramConfigStore({ recordRuntimeEvent });
121
- const isTelegramBusRuntimeEnabled = telegramThreadCapabilityState.isBusRuntimeEnabled;
121
+ const getTelegramUpdateAdmissionScope =
122
+ Journal.createTelegramUpdateJournalReceiptScopeResolver({
123
+ getProfileName: configStore.getActiveProfileName,
124
+ getBotToken: configStore.getBotToken,
125
+ getBotId: getTelegramBotId,
126
+ });
127
+ const telegramJournalBindingRuntime =
128
+ Journal.createTelegramUpdateJournalBindingRuntime({
129
+ base: {
130
+ getProfileName: configStore.getActiveProfileName,
131
+ getBotToken: configStore.getBotToken,
132
+ getBotId: getTelegramBotId,
133
+ getQueueRuntimeIdentity() {
134
+ return {
135
+ instanceId: telegramInstanceId,
136
+ processId: telegramProcessId,
137
+ processBirthId: telegramQueueProcessBirthId,
138
+ };
139
+ },
140
+ },
141
+ getLeaderJournalPath: Paths.resolveTelegramUpdateJournalPath,
142
+ getFollowerJournalPath(bindingKey, profileName) {
143
+ return Paths.resolveTelegramFollowerJournalPath(
144
+ bindingKey,
145
+ undefined,
146
+ profileName,
147
+ );
148
+ },
149
+ getActiveFollowerBindingKey: getTelegramManualFollowerProfileKey,
150
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
151
+ });
152
+ const resolveTelegramUpdateJournalBinding =
153
+ telegramJournalBindingRuntime.resolveLeader;
154
+ const resolveTelegramFollowerJournalBinding =
155
+ telegramJournalBindingRuntime.resolveFollower;
156
+ const getTelegramQueueJournalBinding =
157
+ telegramJournalBindingRuntime.getActiveRecoveryKey;
158
+ const isTelegramBusRuntimeEnabled =
159
+ telegramThreadCapabilityState.isBusRuntimeEnabled;
122
160
  Config.bindGlobalTelegramConfigRuntime(configStore);
123
161
  const configControls = Config.createTelegramConfigControls(configStore);
124
162
  const lockRuntime = Locks.createTelegramLockRuntime<Pi.ExtensionContext>({
@@ -157,7 +195,7 @@ export default function (pi: Pi.ExtensionAPI) {
157
195
  lock: lockRuntime,
158
196
  contextStore: telegramSessionContextStore,
159
197
  });
160
- modelContextAvailabilityRuntime =
198
+ const modelContextAvailabilityRuntime =
161
199
  Prompts.createTelegramModelContextAvailabilityRuntime({
162
200
  getActiveTools,
163
201
  setActiveTools,
@@ -172,6 +210,7 @@ export default function (pi: Pi.ExtensionAPI) {
172
210
  return !ctx || Pi.isExtensionContextIdle(ctx);
173
211
  },
174
212
  });
213
+ modelContextAvailabilityBinding.bind(modelContextAvailabilityRuntime);
175
214
  const activeTurnRuntime = Queue.createTelegramActiveTurnStore();
176
215
  const proactivePushTargetGetter =
177
216
  Config.createTelegramProactivePushTargetGetter({
@@ -222,6 +261,10 @@ export default function (pi: Pi.ExtensionAPI) {
222
261
  rawTelegramQueueStore,
223
262
  telegramTransportStampRuntime.getStamp,
224
263
  );
264
+ const updateAdmissionRuntimeBinding =
265
+ Updates.createTelegramUpdateAdmissionRuntimeBinding<Pi.ExtensionContext>({
266
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
267
+ });
225
268
  const deferredQueueDispatchRuntime =
226
269
  Queue.createTelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>({
227
270
  recordRuntimeEvent,
@@ -244,51 +287,36 @@ export default function (pi: Pi.ExtensionAPI) {
244
287
  configStore,
245
288
  persistTelegramConfigWithSync,
246
289
  );
247
- const currentInstanceThreadRuntime =
248
- Threads.createTelegramCurrentInstanceThreadRuntime({
249
- instanceId: telegramInstanceId,
250
- listRecords: threadStore.list,
251
- getPreferredTarget() {
252
- return (
253
- activeTurnRuntime.getTarget() ??
254
- telegramBusFollowerRegistrationState.getTarget() ??
255
- telegramBusLeaderState.getTarget()
256
- );
257
- },
258
- getFollower() {
259
- const target = telegramBusFollowerRegistrationState.getTarget();
260
- if (!target) return undefined;
261
- return {
262
- registered: telegramBusFollowerRegistrationState.isRegistered(),
263
- target,
264
- slot: telegramBusFollowerRegistrationState.getSlot(),
265
- threadName: telegramBusFollowerRegistrationState.getThreadName(),
266
- };
267
- },
268
- getLeader: telegramBusLeaderState.getIdentity,
269
- });
270
- const findCurrentThreadRecord = currentInstanceThreadRuntime.findRecord;
271
- const getCurrentInstanceThreadIdentity =
272
- currentInstanceThreadRuntime.getIdentity;
273
- const threadStatusProjectionRuntime =
274
- Threads.createTelegramThreadStatusProjectionRuntime({
290
+ const {
291
+ current: currentInstanceThreadRuntime,
292
+ status: threadStatusProjectionRuntime,
293
+ } = Threads.createTelegramCurrentThreadAssembly({
294
+ instanceId: telegramInstanceId,
295
+ listRecords: threadStore.list,
296
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
297
+ getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
298
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
299
+ getFollowerSlot: telegramBusFollowerRegistrationState.getSlot,
300
+ getFollowerThreadName: telegramBusFollowerRegistrationState.getThreadName,
301
+ getLeaderIdentity: telegramBusLeaderState.getIdentity,
302
+ getLeaderTarget: telegramBusLeaderState.getTarget,
303
+ getLeaderProtocol: telegramBusFollowerRegistrationState.getLeaderProtocol,
304
+ status: {
275
305
  getThreadMode: function () {
276
306
  return threadStore.getBotState().threadMode;
277
307
  },
278
308
  isBusPollingStarted: telegramThreadCapabilityState.isBusPollingStarted,
279
- isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
280
309
  listFollowers: telegramBusFollowerRegistry.list,
281
- listRecords: threadStore.list,
282
310
  listReservations: threadStore.listReservations,
283
311
  listSyncObservations: threadStore.listSyncObservations,
284
312
  getLeaderSocketPath: getTelegramBusSocketPath,
285
313
  getFollowerSocketPath: getTelegramBusFollowerSocketPath,
286
314
  getTransportKind: BusTransport.getTelegramBusTransportKind,
287
- getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
288
- getFollowerSlot: telegramBusFollowerRegistrationState.getSlot,
289
- getFollowerThreadName: telegramBusFollowerRegistrationState.getThreadName,
290
- getCurrentIdentity: currentInstanceThreadRuntime.getRestorationIdentity,
291
- });
315
+ },
316
+ });
317
+ const findCurrentThreadRecord = currentInstanceThreadRuntime.findRecord;
318
+ const getCurrentInstanceThreadIdentity =
319
+ currentInstanceThreadRuntime.getIdentity;
292
320
  const statusRuntime = Status.createTelegramBridgeStatusRuntime<
293
321
  Pi.ExtensionContext,
294
322
  Queue.TelegramQueueItem<Pi.ExtensionContext>
@@ -299,6 +327,12 @@ export default function (pi: Pi.ExtensionAPI) {
299
327
  isPollingActive: Polling.createTelegramPollingActivityReader(
300
328
  pollingControllerState,
301
329
  ),
330
+ getPollingState: Polling.createTelegramPollingStateReader(
331
+ pollingControllerState,
332
+ ),
333
+ getInboundWorkerState() {
334
+ return updateAdmissionRuntimeBinding.getActive()?.getState();
335
+ },
302
336
  getActiveSourceMessageIds: activeTurnRuntime.getSourceMessageIds,
303
337
  hasActiveTurn: activeTurnRuntime.has,
304
338
  hasDispatchPending: lifecycle.hasDispatchPending,
@@ -310,6 +344,9 @@ export default function (pi: Pi.ExtensionAPI) {
310
344
  getRecentRuntimeEvents: runtimeEvents.getEvents,
311
345
  getRuntimeLockState: lockRuntime.getStatusLabel,
312
346
  ...threadStatusProjectionRuntime,
347
+ getBusProtocol() {
348
+ return telegramBusProtocolIdentity;
349
+ },
313
350
  getBusLifecyclePhase: telegramBusFollowerControlState.getLifecyclePhase,
314
351
  getBotThreadMode() {
315
352
  return threadStore.getBotState();
@@ -407,19 +444,13 @@ export default function (pi: Pi.ExtensionAPI) {
407
444
  sendAggregateTypingAction:
408
445
  BusApi.createTelegramAggregateTypingActionSender(telegramApiRuntime),
409
446
  updateStatus,
447
+ isContextActive: telegramSessionContextStore.isCurrent,
410
448
  recordRuntimeEvent,
411
449
  });
412
450
  const currentModelRuntime = Model.createCurrentModelRuntime({
413
451
  getContextModel,
414
452
  updateStatus,
415
453
  });
416
- const queueMutationRuntime = Queue.createTelegramQueueMutationController({
417
- ...telegramQueueStore,
418
- getNextPriorityReactionOrder: queue.getNextPriorityReactionOrder,
419
- incrementNextPriorityReactionOrder:
420
- queue.incrementNextPriorityReactionOrder,
421
- updateStatus,
422
- });
423
454
 
424
455
  // --- Reply Runtime & Preview ---
425
456
 
@@ -469,11 +500,13 @@ export default function (pi: Pi.ExtensionAPI) {
469
500
  getHandlers: configStore.getOutboundHandlers,
470
501
  recordRuntimeEvent,
471
502
  });
472
- let activityVerbosityRuntime:
473
- | ActivityVerbosity.TelegramActivityVerbosityRuntime
474
- | undefined;
475
- const assistantOutputBindingRuntime =
476
- Bindings.createTelegramAssistantOutputBindingRuntime({
503
+ const {
504
+ activityRuntime,
505
+ activityVerbosityRuntime,
506
+ assistantOutputRuntime,
507
+ } = Bindings.createTelegramActivityBindingRuntime({
508
+ generation: deliveryGenerationSeed,
509
+ assistantOutput: {
477
510
  isEnabled: configControls.isProactivePushEnabled,
478
511
  authority: {
479
512
  getPreferredTarget: proactivePushTargetGetter,
@@ -496,74 +529,39 @@ export default function (pi: Pi.ExtensionAPI) {
496
529
  getHandlers: configStore.getOutboundHandlers,
497
530
  recordRuntimeEvent,
498
531
  },
499
- waitForActivityIdle() {
500
- return activityVerbosityRuntime?.waitForIdle() ?? Promise.resolve();
501
- },
502
532
  recordRuntimeEvent,
503
- });
504
- const assistantOutputRuntime = assistantOutputBindingRuntime.runtime;
505
- activityVerbosityRuntime =
506
- ActivityVerbosity.createTelegramActivityVerbosityRuntime({
533
+ },
534
+ activityVerbosity: {
507
535
  getActivityMode: configControls.getActivityVerbosity,
508
536
  refreshActivityMode: configControls.refreshActivityVerbosity,
509
537
  resolveTarget(event) {
510
538
  return event.target ?? proactivePushTargetGetter();
511
539
  },
512
- captureAuthority: assistantOutputBindingRuntime.authority.captureAuthority,
513
- isAuthorityActive:
514
- assistantOutputBindingRuntime.authority.isAuthorityActive,
515
540
  sendMessage,
516
541
  sendRichMessage,
517
542
  editMessageText: editTelegramMessageText,
518
- recordFailure(operation, event, error) {
519
- recordRuntimeEvent("activity", error, {
520
- operation,
521
- eventType: event.type,
522
- activityId: event.activityId,
523
- });
524
- },
525
- });
526
- const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
527
- generation: deliveryGenerationSeed,
528
- observeEvent(event) {
529
- assistantOutputBindingRuntime.observeEvent(event);
530
- activityVerbosityRuntime.accept(event);
531
- },
532
- recordFailure(handlerId, event, error) {
533
- recordRuntimeEvent("activity", error, {
534
- handlerId,
535
- eventType: event.type,
536
- activityId: event.activityId,
537
- });
538
543
  },
539
544
  });
540
- const dispatchNextQueuedTelegramTurn =
541
- Queue.createTelegramQueueDispatchRuntime({
542
- ...telegramQueueStore,
543
- isCompactionInProgress: lifecycle.isCompactionInProgress,
544
- hasActiveTurn: activeTurnRuntime.has,
545
- hasDispatchPending: lifecycle.hasDispatchPending,
546
- isIdle,
547
- hasPendingMessages,
548
- hasDispatchContext: deferredQueueDispatchRuntime.isBound,
549
- getDispatchGeneration: deferredQueueDispatchRuntime.getGeneration,
550
- isDispatchGenerationActive:
551
- deferredQueueDispatchRuntime.isGenerationActive,
552
- isQueueItemTransportActive(item) {
553
- return telegramTransportStampRuntime.isActive(item.transportStamp);
554
- },
555
- updateStatus,
556
- sendTextReply,
557
- recordRuntimeEvent,
558
- ...promptDispatchRuntime,
559
- sendUserMessage,
560
- }).dispatchNext;
561
- const queueDispatchWatchdogRuntime =
562
- Queue.createTelegramQueueDispatchWatchdogRuntime({
563
- hasQueuedItems: telegramQueueStore.hasQueuedItems,
564
- dispatchNextQueuedTelegramTurn,
565
- recordRuntimeEvent,
566
- });
545
+ const {
546
+ mutation: queueMutationRuntime,
547
+ dispatchNext: dispatchNextQueuedTelegramTurn,
548
+ watchdog: queueDispatchWatchdogRuntime,
549
+ } = Bindings.createTelegramQueueBindingRuntime({
550
+ store: telegramQueueStore,
551
+ queue,
552
+ lifecycle,
553
+ activeTurn: activeTurnRuntime,
554
+ admission: updateAdmissionRuntimeBinding,
555
+ transportStamp: telegramTransportStampRuntime,
556
+ deferredDispatch: deferredQueueDispatchRuntime,
557
+ promptDispatch: promptDispatchRuntime,
558
+ isIdle,
559
+ hasPendingMessages,
560
+ updateStatus,
561
+ sendTextReply,
562
+ sendUserMessage,
563
+ recordRuntimeEvent,
564
+ });
567
565
  const nativeMarkdownDraftSender =
568
566
  TelegramApi.createTelegramAssistantDraftSender({
569
567
  getAssistantRenderingMode: configControls.getAssistantRenderingMode,
@@ -705,6 +703,9 @@ export default function (pi: Pi.ExtensionAPI) {
705
703
  getCurrentLeaderEpoch,
706
704
  getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
707
705
  recordThreadReconciliationPlan,
706
+ assertExecutionCurrent(message) {
707
+ Updates.assertTelegramUpdateExecutionCurrent(message);
708
+ },
708
709
  getSyncState: telegramSyncStateRuntime.getState,
709
710
  setSyncState: telegramSyncStateRuntime.setState,
710
711
  recordEvent: recordRuntimeEvent,
@@ -725,6 +726,8 @@ export default function (pi: Pi.ExtensionAPI) {
725
726
  getCurrentInstanceId() {
726
727
  return telegramInstanceId;
727
728
  },
729
+ getAdmissionScope: getTelegramUpdateAdmissionScope,
730
+ getAdmissionJournalBinding: getTelegramQueueJournalBinding,
728
731
  getMessageOwnership: messageOwnershipRuntime.store.get,
729
732
  recordMessageOwnership: messageOwnershipRuntime.recordRouted,
730
733
  ...inboundBusProjectionRuntime,
@@ -758,6 +761,7 @@ export default function (pi: Pi.ExtensionAPI) {
758
761
  inboundHandlerRuntime,
759
762
  threadStore,
760
763
  updateStatus,
764
+ isContextActive: telegramSessionContextStore.isCurrent,
761
765
  dispatchNextQueuedTelegramTurn,
762
766
  requestDeferredDispatchNextQueuedTelegramTurn:
763
767
  deferredQueueDispatchRuntime.request,
@@ -788,11 +792,90 @@ export default function (pi: Pi.ExtensionAPI) {
788
792
  compact,
789
793
  recordRuntimeEvent,
790
794
  });
795
+ const queueHandoffReconciliationBinding =
796
+ Updates.createTelegramQueueHandoffReconciliationBinding<Pi.ExtensionContext>(
797
+ function (error) {
798
+ recordRuntimeEvent("inbound-worker", error, {
799
+ phase: "queue-handoff-reconcile",
800
+ });
801
+ },
802
+ );
803
+ const {
804
+ owner: updateWorkerOwnerRuntime,
805
+ leader: updateAdmissionLifecycleRuntime,
806
+ follower: followerAdmissionLifecycleRuntime,
807
+ } = Updates.createTelegramUpdateAdmissionRuntimeAssembly<
808
+ Parameters<typeof inboundRouteRuntime.handleUpdate>[0] &
809
+ Journal.TelegramJournaledUpdate,
810
+ Pi.ExtensionContext
811
+ >({
812
+ runtimeBinding: updateAdmissionRuntimeBinding,
813
+ owner: {
814
+ instanceId: telegramInstanceId,
815
+ processId: telegramProcessId,
816
+ processBirthId: telegramQueueProcessBirthId,
817
+ getSessionGeneration: telegramSessionContextStore.getGeneration,
818
+ isContextCurrent: telegramSessionContextStore.isCurrent,
819
+ dispatchNext: dispatchNextQueuedTelegramTurn,
820
+ requestQueueHandoffReconciliation:
821
+ queueHandoffReconciliationBinding.request,
822
+ },
823
+ worker: {
824
+ defaultHandle: inboundRouteRuntime.handleUpdate,
825
+ onStateChange: runtimeDiagnostics.scheduleSnapshotPersist,
826
+ },
827
+ leader: {
828
+ resolveBinding: resolveTelegramUpdateJournalBinding,
829
+ hasAuthority: lockRuntime.owns,
830
+ },
831
+ follower: {
832
+ resolveBinding: resolveTelegramFollowerJournalBinding,
833
+ isRegistered: telegramBusFollowerRegistrationState.isRegistered,
834
+ getGeneration: telegramBusFollowerRegistrationState.getGeneration,
835
+ prepareUpdateForExecution(update) {
836
+ return BusFollower.prepareTelegramBusFollowerJournaledUpdateForExecution(
837
+ update,
838
+ textGroupRuntime.prepareForwardedMessage,
839
+ );
840
+ },
841
+ },
842
+ recordRuntimeEvent,
843
+ });
844
+ const queueHandoffStagingRuntime =
845
+ Queue.createTelegramQueueHandoffStagingRuntime({
846
+ liveStore: telegramQueueStore,
847
+ createControlExecution:
848
+ Updates.createTelegramQueueHandoffControlExecutionFactory({
849
+ isContextCurrent: telegramSessionContextStore.isCurrent,
850
+ showStatus: menuActions.sendStatusMessage,
851
+ openModelMenu: menuActions.openModelMenu,
852
+ }),
853
+ });
854
+ const acceptStagedQueueHandoff =
855
+ Updates.createTelegramQueueHandoffRecipientRuntime({
856
+ staging: queueHandoffStagingRuntime,
857
+ getRecipientOwner: updateWorkerOwnerRuntime.getQueueOwnerIdentity,
858
+ getLifecycleForBinding:
859
+ updateAdmissionRuntimeBinding.getLifecycleForJournalBinding,
860
+ isTransportStampActive: telegramTransportStampRuntime.isActive,
861
+ dispatchNext: dispatchNextQueuedTelegramTurn,
862
+ });
863
+ const followerDurableAdmissionRuntime =
864
+ BusFollower.createTelegramBusFollowerDurableAdmissionRuntime({
865
+ journal: {
866
+ appendBatch(updates) {
867
+ return followerAdmissionLifecycleRuntime.appendBatch(updates);
868
+ },
869
+ },
870
+ signalWorker() {
871
+ followerAdmissionLifecycleRuntime.signal();
872
+ },
873
+ });
791
874
  const promoteTelegramBusFollowerToLeader: BusFollower.TelegramBusFollowerPromotionHandler<Pi.ExtensionContext> =
792
875
  BusFollower.createTelegramBusFollowerPromotionHandler<Pi.ExtensionContext>({
793
876
  topicTargetStore: threadStore,
794
877
  instanceId: telegramInstanceId,
795
- getActiveProfileName: getActiveTelegramThreadProfile,
878
+ getActiveProfileName: configStore.getActiveProfileName,
796
879
  async startLeader(ctx, election, onAcquired): Promise<boolean> {
797
880
  const result = await lockedPollingRuntime.start(ctx, {
798
881
  election,
@@ -802,17 +885,6 @@ export default function (pi: Pi.ExtensionAPI) {
802
885
  },
803
886
  recordRuntimeEvent,
804
887
  });
805
- const forwardedRouteHandlers =
806
- BusFollower.createTelegramBusForwardedRouteHandlers<
807
- Pi.ExtensionContext,
808
- Updates.TelegramMessageReactionUpdated,
809
- Routing.TelegramRoutedCallbackQuery,
810
- Routing.TelegramRoutedMessage
811
- >({
812
- handleUpdate: inboundRouteRuntime.handleUpdate,
813
- handleAuthorizedReactionUpdate:
814
- inboundRouteRuntime.handleAuthorizedReactionUpdate,
815
- });
816
888
  const agentMessageRuntime = AgentMessages.createTelegramAgentMessageRuntime({
817
889
  instanceId: telegramInstanceId,
818
890
  getAllowedChatId: configStore.getAllowedUserId,
@@ -824,13 +896,20 @@ export default function (pi: Pi.ExtensionAPI) {
824
896
  getContext: telegramSessionContextStore.get,
825
897
  handleUpdate: inboundRouteRuntime.handleUpdate,
826
898
  });
827
- const telegramBusFollowerAssemblyDeps =
828
- BusFollower.createTelegramBusFollowerRuntimeAssemblyDeps<
829
- Pi.ExtensionContext,
830
- Updates.TelegramMessageReactionUpdated,
831
- Routing.TelegramRoutedCallbackQuery,
832
- Routing.TelegramRoutedMessage
833
- >({
899
+ const agentMessageToolRoutingRuntime =
900
+ Bindings.createTelegramAgentMessageToolRoutingRuntime({
901
+ ownsLeader: lockRuntime.owns,
902
+ ownsDirectDelivery: ownsTelegramDirectDelivery,
903
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
904
+ getSourceTarget: proactivePushTargetGetter,
905
+ getSourceThreadName() {
906
+ return findCurrentThreadRecord()?.threadName;
907
+ },
908
+ local: agentMessageRuntime,
909
+ follower: telegramBusFollowerClients.agentMessages,
910
+ });
911
+ const telegramBusFollowerAssembly: BusFollower.TelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext> =
912
+ BusFollower.createTelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext>({
834
913
  instanceId: telegramInstanceId,
835
914
  registrationState: telegramBusFollowerRegistrationState,
836
915
  recordRuntimeEvent,
@@ -838,8 +917,9 @@ export default function (pi: Pi.ExtensionAPI) {
838
917
  socketPath: getTelegramBusFollowerSocketPath,
839
918
  getContext: telegramSessionContextStore.get,
840
919
  getAuthSecret: telegramBusFollowerControlState.getActiveAuthSecret,
841
- ...forwardedRouteHandlers,
842
- prepareForwardedMessage: textGroupRuntime.prepareForwardedMessage,
920
+ getRecipientBindingKey: getTelegramManualFollowerProfileKey,
921
+ durableAdmission: followerDurableAdmissionRuntime,
922
+ handleQueueHandoff: acceptStagedQueueHandoff,
843
923
  },
844
924
  targetReplacement: {
845
925
  topicTargetStore: threadStore,
@@ -857,38 +937,59 @@ export default function (pi: Pi.ExtensionAPI) {
857
937
  getActiveContext: telegramSessionContextStore.get,
858
938
  },
859
939
  registration: {
940
+ protocolIdentity: telegramBusProtocolIdentity,
860
941
  getFollowerBusSocketPath: getTelegramBusFollowerSocketPath,
861
942
  getLeaderSocketPath: getTelegramBusSocketPath,
862
943
  isContextActive: telegramSessionContextStore.isCurrent,
863
944
  createRequestId: telegramBusFollowerClients.createRequestId,
864
- setActiveAuthSecret: telegramBusFollowerControlState.setActiveAuthSecret,
945
+ setActiveAuthSecret:
946
+ telegramBusFollowerControlState.setActiveAuthSecret,
865
947
  getProfileKey: getTelegramManualFollowerProfileKey,
948
+ getProcessBirthId() {
949
+ return telegramQueueProcessBirthId;
950
+ },
951
+ getSessionGeneration: telegramSessionContextStore.getGeneration,
952
+ async onRegistered(ctx) {
953
+ await followerAdmissionLifecycleRuntime.onTransportChanged(ctx);
954
+ queueHandoffReconciliationBinding.request(ctx);
955
+ },
866
956
  },
867
957
  });
868
- const telegramBusFollowerAssembly: BusFollower.TelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext> =
869
- BusFollower.createTelegramBusFollowerRuntimeAssembly<
870
- Pi.ExtensionContext,
871
- Updates.TelegramMessageReactionUpdated,
872
- Routing.TelegramRoutedCallbackQuery,
873
- Routing.TelegramRoutedMessage
874
- >(telegramBusFollowerAssemblyDeps);
875
958
  const telegramBusFollowerRegistration =
876
959
  telegramBusFollowerAssembly.registration;
877
- const pollingRuntime = Polling.createTelegramPollingControllerRuntime({
878
- state: pollingControllerState,
879
- getConfig: configStore.get,
880
- hasBotToken: configStore.hasBotToken,
881
- deleteWebhook,
882
- getUpdates,
883
- persistConfig: persistTelegramPollingOffset,
884
- prepareUpdateBatch: textGroupRuntime.prepareUpdateBatch,
885
- handleUpdate: Updates.createTelegramUpdateHandle({
886
- defaultHandle: inboundRouteRuntime.handleUpdate,
887
- }),
888
- stopTypingLoop: typing.stop,
889
- updateStatus,
890
- recordRuntimeEvent,
891
- });
960
+ const { admission: pollingAdmissionRuntime } =
961
+ Polling.createTelegramDurablePollingRuntimeAssembly<
962
+ TelegramApi.TelegramUpdate,
963
+ Pi.ExtensionContext
964
+ >({
965
+ state: pollingControllerState,
966
+ getConfig: configStore.get,
967
+ hasBotToken: configStore.hasBotToken,
968
+ deleteWebhook,
969
+ getUpdates,
970
+ persistConfig: persistTelegramPollingOffset,
971
+ prepareUpdateBatch: textGroupRuntime.prepareUpdateBatch,
972
+ journal: {
973
+ appendBatch(updates) {
974
+ return updateAdmissionLifecycleRuntime.appendBatch(
975
+ updates as Journal.TelegramJournaledUpdate[],
976
+ );
977
+ },
978
+ getEntryCount: updateAdmissionLifecycleRuntime.getJournalEntryCount,
979
+ signalWorker: updateAdmissionLifecycleRuntime.signal,
980
+ getBootstrapEntryCount() {
981
+ return (
982
+ resolveTelegramUpdateJournalBinding()?.journal.read().entries
983
+ .length ?? 0
984
+ );
985
+ },
986
+ onSessionStart: updateAdmissionLifecycleRuntime.onSessionStart,
987
+ },
988
+ stopTypingLoop: typing.stop,
989
+ updateStatus,
990
+ onPollingStateChange: runtimeDiagnostics.scheduleSnapshotPersist,
991
+ recordRuntimeEvent,
992
+ });
892
993
  const recoverStaleTelegramTopicApiError =
893
994
  Sync.createTelegramStaleTopicApiErrorRecoveryRuntime({
894
995
  topicTargetStore: threadStore,
@@ -908,8 +1009,9 @@ export default function (pi: Pi.ExtensionAPI) {
908
1009
  },
909
1010
  followerRegistry: telegramBusFollowerRegistry,
910
1011
  authSecret: telegramBusAuthSecret,
911
- startPolling: pollingRuntime.start,
912
- stopPolling: pollingRuntime.stop,
1012
+ protocolIdentity: telegramBusProtocolIdentity,
1013
+ startPolling: pollingAdmissionRuntime.start,
1014
+ stopPolling: pollingAdmissionRuntime.stop,
913
1015
  authorizeFollowerApiCall,
914
1016
  resolveAgentTarget(follower, selector) {
915
1017
  return agentMessageRuntime.resolveTarget(selector, follower.target);
@@ -931,7 +1033,7 @@ export default function (pi: Pi.ExtensionAPI) {
931
1033
  getAllowedUserId: configStore.getAllowedUserId,
932
1034
  instanceId: telegramInstanceId,
933
1035
  getCwd: Pi.getExtensionContextCwd,
934
- getTelegramProfile: getActiveTelegramThreadProfile,
1036
+ getTelegramProfile: configStore.getActiveProfileName,
935
1037
  shouldForceFreshUnnamed:
936
1038
  telegramThreadCapabilityState.shouldForceFreshLeaderThread,
937
1039
  topicTargetStore: threadStore,
@@ -951,6 +1053,31 @@ export default function (pi: Pi.ExtensionAPI) {
951
1053
  onProvisioningEnd: telegramProvisioningActivity.end,
952
1054
  recordRuntimeEvent,
953
1055
  });
1056
+ queueHandoffReconciliationBinding.set(
1057
+ Updates.createTelegramQueueHandoffReconciliationRuntimeAssembly({
1058
+ ownsDirect: lockRuntime.owns,
1059
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
1060
+ isBusEnabled: isTelegramBusRuntimeEnabled,
1061
+ canHandoffWithLeader() {
1062
+ return Bus.hasTelegramBusCapability(
1063
+ telegramBusFollowerRegistrationState.getLeaderProtocol(),
1064
+ Bus.TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
1065
+ );
1066
+ },
1067
+ listFollowers: telegramBusFollowerRegistry.list,
1068
+ createRecipientJournalResolver:
1069
+ telegramJournalBindingRuntime.createRecipientResolver,
1070
+ queueStore: telegramQueueStore,
1071
+ admission: updateAdmissionRuntimeBinding,
1072
+ createHandoffToken: Journal.createTelegramUpdateQueueHandoffToken,
1073
+ createRequestId: telegramBusFollowerClients.createRequestId,
1074
+ donorInstanceId: telegramInstanceId,
1075
+ authSecret: telegramBusAuthSecret,
1076
+ stageThroughFollower: telegramBusFollowerClients.queueHandoff,
1077
+ routeThroughLeader: telegramBusLeaderRuntime.routeQueueHandoff,
1078
+ recordRuntimeEvent,
1079
+ }),
1080
+ );
954
1081
  const telegramLeaderHealthRuntime = Sync.createTelegramLeaderHealthRuntime({
955
1082
  callGetMe() {
956
1083
  return directTelegramApiRuntime.call("getMe", {});
@@ -970,8 +1097,9 @@ export default function (pi: Pi.ExtensionAPI) {
970
1097
  topicTargetStore: threadStore,
971
1098
  isBusRuntimeEnabled: isTelegramBusRuntimeEnabled,
972
1099
  ownsLock: lockRuntime.owns,
973
- startClassicPolling: pollingRuntime.start,
974
- stopClassicPolling: pollingRuntime.stop,
1100
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
1101
+ startClassicPolling: pollingAdmissionRuntime.start,
1102
+ stopClassicPolling: pollingAdmissionRuntime.stop,
975
1103
  startBusLeaderPolling: telegramBusLeaderRuntime.startPolling,
976
1104
  stopBusLeaderPolling: telegramBusLeaderRuntime.stopPolling,
977
1105
  startLeaderHealth: telegramLeaderHealthRuntime.start,
@@ -999,27 +1127,33 @@ export default function (pi: Pi.ExtensionAPI) {
999
1127
  registerFollowerWithOwner:
1000
1128
  threadAwarePollingPorts.registerFollowerWithOwner,
1001
1129
  stopFollowerRegistration: threadAwarePollingPorts.stopFollowerRegistration,
1002
- onTransportAvailabilityChanged:
1003
- modelContextAvailabilityRuntime.reconcile,
1130
+ onTransportAvailabilityChanged() {
1131
+ modelContextAvailabilityRuntime.reconcile();
1132
+ const ctx = telegramSessionContextStore.get();
1133
+ if (ctx) queueHandoffReconciliationBinding.request(ctx);
1134
+ },
1004
1135
  updateStatus,
1005
1136
  recordRuntimeEvent,
1006
1137
  });
1007
- const disconnectTelegramAndDeleteCurrentThread =
1008
- Sync.createTelegramManualThreadDisconnectHandler({
1009
- instanceId: telegramInstanceId,
1010
- getCurrentThreadRecord: findCurrentThreadRecord,
1011
- topicTargetStore: threadStore,
1012
- callApi: callTelegramApi,
1013
- getCurrentLeaderEpoch,
1014
- getLeaderTarget: telegramBusLeaderState.getTarget,
1015
- clearLeaderTarget: telegramBusLeaderState.clear,
1016
- disconnectFollowerThread:
1017
- telegramBusFollowerRegistration.disconnectFromLeader,
1018
- getSyncState: telegramSyncStateRuntime.getState,
1019
- setSyncState: telegramSyncStateRuntime.setState,
1020
- stopPolling: lockedPollingRuntime.stop,
1021
- recordRuntimeEvent,
1022
- });
1138
+ const {
1139
+ disconnect: disconnectTelegramAndDeleteCurrentThread,
1140
+ cleanupForSessionRestart: cleanupTelegramThreadForSessionRestart,
1141
+ } = Sync.createTelegramThreadDisconnectAssembly({
1142
+ instanceId: telegramInstanceId,
1143
+ getCurrentThreadRecord: findCurrentThreadRecord,
1144
+ topicTargetStore: threadStore,
1145
+ callApi: callTelegramApi,
1146
+ getCurrentLeaderEpoch,
1147
+ getLeaderTarget: telegramBusLeaderState.getTarget,
1148
+ clearLeaderTarget: telegramBusLeaderState.clear,
1149
+ disconnectFollowerThread:
1150
+ telegramBusFollowerRegistration.disconnectFromLeader,
1151
+ getSyncState: telegramSyncStateRuntime.getState,
1152
+ setSyncState: telegramSyncStateRuntime.setState,
1153
+ stopPolling: lockedPollingRuntime.stop,
1154
+ suspendPolling: lockedPollingRuntime.suspend,
1155
+ recordRuntimeEvent,
1156
+ });
1023
1157
  const telegramBridgeSessionLifecycleDeps =
1024
1158
  Lifecycle.createTelegramBridgeSessionLifecycleDeps({
1025
1159
  contextStore: telegramSessionContextStore,
@@ -1051,7 +1185,7 @@ export default function (pi: Pi.ExtensionAPI) {
1051
1185
  isLeader: lockRuntime.owns,
1052
1186
  getLeaderBinding: currentInstanceThreadRuntime.getRestorationIdentity,
1053
1187
  getActiveContext: telegramSessionContextStore.get,
1054
- getActiveProfileName: getActiveTelegramThreadProfile,
1188
+ getActiveProfileName: configStore.getActiveProfileName,
1055
1189
  getLeaderState: lockRuntime.getState,
1056
1190
  updateStatus,
1057
1191
  recordRuntimeEvent,
@@ -1067,6 +1201,9 @@ export default function (pi: Pi.ExtensionAPI) {
1067
1201
  },
1068
1202
  delivery: deliveryLifecycleRuntime,
1069
1203
  polling: lockedPollingRuntime,
1204
+ inboundWorker: {
1205
+ onSessionShutdown: updateAdmissionRuntimeBinding.onSessionShutdown,
1206
+ },
1070
1207
  capabilityMonitor: telegramThreadCapabilityMonitor,
1071
1208
  queueWatchdog: queueDispatchWatchdogRuntime,
1072
1209
  },
@@ -1086,21 +1223,20 @@ export default function (pi: Pi.ExtensionAPI) {
1086
1223
  activeTurnRuntime,
1087
1224
  lockedPollingRuntime,
1088
1225
  stopPolling: disconnectTelegramAndDeleteCurrentThread,
1089
- recoverPollingStart:
1090
- Recovery.createTelegramPollingStartRecoveryHandler({
1091
- getOwnersPath: Paths.resolveTelegramOwnersPath,
1092
- getStatePaths() {
1093
- return [
1094
- Threads.getTelegramTopicTargetsPath(
1095
- undefined,
1096
- configStore.getActiveProfileName(),
1097
- ),
1098
- ];
1099
- },
1100
- suspendPolling: lockedPollingRuntime.suspend,
1101
- releaseOwnership: lockRuntime.release,
1102
- recordRuntimeEvent,
1103
- }),
1226
+ recoverPollingStart: Recovery.createTelegramPollingStartRecoveryHandler({
1227
+ getOwnersPath: Paths.resolveTelegramOwnersPath,
1228
+ getStatePaths() {
1229
+ return [
1230
+ Threads.getTelegramTopicTargetsPath(
1231
+ undefined,
1232
+ configStore.getActiveProfileName(),
1233
+ ),
1234
+ ];
1235
+ },
1236
+ suspendPolling: lockedPollingRuntime.suspend,
1237
+ releaseOwnership: lockRuntime.release,
1238
+ recordRuntimeEvent,
1239
+ }),
1104
1240
  getDisconnectThreadName() {
1105
1241
  const record = findCurrentThreadRecord();
1106
1242
  if (!record?.target.threadId) return undefined;
@@ -1117,38 +1253,7 @@ export default function (pi: Pi.ExtensionAPI) {
1117
1253
  callMultipart,
1118
1254
  getDefaultChatId: proactivePushChatIdGetter,
1119
1255
  getDefaultTarget: proactivePushTargetGetter,
1120
- async resolveAgentTarget(selector) {
1121
- if (lockRuntime.owns()) {
1122
- const target = agentMessageRuntime.resolveTarget(
1123
- selector,
1124
- proactivePushTargetGetter(),
1125
- );
1126
- if (!target) {
1127
- throw new Error(
1128
- "Telegram agent target is unavailable, ambiguous, or not live.",
1129
- );
1130
- }
1131
- return target;
1132
- }
1133
- return telegramBusFollowerClients.agentMessages.resolveTarget(selector);
1134
- },
1135
- async routeAgentMessage(message) {
1136
- if (lockRuntime.owns()) {
1137
- await agentMessageRuntime.route({
1138
- sourceTarget: proactivePushTargetGetter(),
1139
- sourceThreadName: findCurrentThreadRecord()?.threadName,
1140
- message,
1141
- });
1142
- return;
1143
- }
1144
- await telegramBusFollowerClients.agentMessages.routeMessage(message);
1145
- },
1146
- canSendDirect() {
1147
- return (
1148
- ownsTelegramDirectDelivery() ||
1149
- telegramBusFollowerRegistrationState.isRegistered()
1150
- );
1151
- },
1256
+ ...agentMessageToolRoutingRuntime,
1152
1257
  updateStatus,
1153
1258
  recordRuntimeEvent,
1154
1259
  });
@@ -1175,7 +1280,7 @@ export default function (pi: Pi.ExtensionAPI) {
1175
1280
  promptDispatchRuntime,
1176
1281
  deferredQueueDispatchRuntime,
1177
1282
  modelContextAvailabilityRuntime,
1178
- disconnectOnQuit: disconnectTelegramAndDeleteCurrentThread,
1283
+ disconnectOnQuit: cleanupTelegramThreadForSessionRestart,
1179
1284
  resolveAutomaticThreadCleanupEnabled:
1180
1285
  configControls.resolveAutomaticThreadCleanupEnabled,
1181
1286
  buttonActionStore,
@@ -1185,6 +1290,11 @@ export default function (pi: Pi.ExtensionAPI) {
1185
1290
  sendMarkdownReply,
1186
1291
  sendTextReply,
1187
1292
  dispatchNextQueuedTelegramTurn,
1293
+ onPromptHandedOff(turn, ctx) {
1294
+ updateAdmissionRuntimeBinding
1295
+ .getSettlement()
1296
+ ?.onPromptHandedOff(turn, ctx);
1297
+ },
1188
1298
  answerGuestQuery,
1189
1299
  deleteMessage: deleteTelegramMessage,
1190
1300
  sendGuestReply,