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