@llblab/pi-kit 0.6.0 → 0.7.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +13 -9
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
  87. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  88. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  89. package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
  90. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
  91. package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
  92. package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
  93. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
  94. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  95. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  96. package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
  97. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  98. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
  99. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  100. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  101. package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
  102. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
  103. package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
  104. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
  105. package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
  106. package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
  107. package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
  108. package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
  109. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  110. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
  111. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  112. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  113. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  114. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  115. package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
  116. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  117. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  118. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  119. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  120. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  121. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  122. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  123. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  124. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  125. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  126. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  127. package/node_modules/@llblab/skills/package.json +2 -3
  128. package/package.json +6 -5
  129. /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
@@ -0,0 +1,1851 @@
1
+ /**
2
+ * Telegram bridge extension composition and orchestration layer
3
+ * Zones: telegram, pi agent, orchestration
4
+ * Keeps runtime wiring in one place while the package entrypoint remains a thin re-export
5
+ */
6
+
7
+ import * as AgentMessages from "./agent-messages.ts";
8
+ import * as Bindings from "./bindings.ts";
9
+ import * as BusApi from "./bus-api.ts";
10
+ import * as BusFollower from "./bus-follower.ts";
11
+ import * as BusLeader from "./bus-leader.ts";
12
+ import * as BusTransport from "./bus-transport.ts";
13
+ import * as Bus from "./bus.ts";
14
+ import * as ChannelPosts from "./channel-posts.ts";
15
+ import * as ThreadCleanupManager from "./thread-cleanup-manager.ts";
16
+ import * as CommandTemplates from "./command-templates.ts";
17
+ import * as Commands from "./commands.ts";
18
+ import * as Config from "./config.ts";
19
+ import * as Delivery from "./delivery.ts";
20
+ import * as Inbound from "./inbound.ts";
21
+ import * as Journal from "./journal.ts";
22
+ import * as Lifecycle from "./lifecycle.ts";
23
+ import * as Locks from "./locks.ts";
24
+ import * as Logging from "./logging.ts";
25
+ import * as Media from "./media.ts";
26
+ import * as MenuQueue from "./menu-queue.ts";
27
+ import * as MenuSettings from "./menu-settings.ts";
28
+ import * as Menu from "./menu.ts";
29
+ import * as Model from "./model.ts";
30
+ import * as Outbound from "./outbound.ts";
31
+ import * as Ownership from "./ownership.ts";
32
+ import * as Paths from "./paths.ts";
33
+ import * as Pi from "./pi.ts";
34
+ import * as Polling from "./polling.ts";
35
+ import * as Preview from "./preview.ts";
36
+ import * as PromptTemplates from "./prompt-templates.ts";
37
+ import * as Prompts from "./prompts.ts";
38
+ import * as Queue from "./queue.ts";
39
+ import * as Recovery from "./recovery.ts";
40
+ import * as Replies from "./replies.ts";
41
+ import * as Routing from "./routing.ts";
42
+ import * as Runtime from "./runtime.ts";
43
+ import * as Sections from "./sections.ts";
44
+ import * as Skills from "./skills.ts";
45
+ import * as Status from "./status.ts";
46
+ import * as Sync from "./sync.ts";
47
+ import * as TelegramApi from "./telegram-api.ts";
48
+ import * as TextGroups from "./text-groups.ts";
49
+ import * as ThreadReconciler from "./thread-reconciler.ts";
50
+ import * as ThreadDisplay from "./thread-display.ts";
51
+ import * as Threads from "./threads.ts";
52
+ import * as TimeInjection from "./time-injection.ts";
53
+ import * as Updates from "./updates.ts";
54
+ import * as Voice from "./voice.ts";
55
+ import * as WorkspaceAdmission from "./workspace-admission.ts";
56
+ import * as WorkspaceRetirement from "./workspace-retirement.ts";
57
+
58
+ type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
59
+
60
+ const telegramBusProtocolIdentity =
61
+ Bus.createTelegramCurrentBusProtocolIdentity([
62
+ Bus.TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
63
+ Bus.TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
64
+ Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT,
65
+ Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME,
66
+ Bus.TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE,
67
+ ]);
68
+
69
+ // --- Extension Runtime ---
70
+
71
+ export default function (pi: Pi.ExtensionAPI) {
72
+ Skills.registerTelegramSkillDiscovery(pi);
73
+ const piRuntime = Pi.createExtensionApiRuntimePorts(pi);
74
+ const {
75
+ getActiveTools,
76
+ getCommands,
77
+ getThinkingLevel,
78
+ sendUserMessage,
79
+ setActiveTools,
80
+ setModel,
81
+ setThinkingLevel,
82
+ } = piRuntime;
83
+ const bridgeRuntime = Runtime.createTelegramBridgeRuntime();
84
+ const runtimeDiagnostics =
85
+ Logging.createTelegramRuntimeDiagnosticsRuntime<Pi.ExtensionContext>();
86
+ const runtimeEvents = runtimeDiagnostics.events;
87
+ const recordRuntimeEvent = runtimeDiagnostics.recordRuntimeEvent;
88
+ const configStore = Config.createTelegramConfigStore({ recordRuntimeEvent });
89
+ const busProcessRuntime = Bus.createCurrentTelegramBusProcessRuntime({
90
+ getActiveProfileName: configStore.getActiveProfileName,
91
+ });
92
+ const {
93
+ instanceId: telegramInstanceId,
94
+ processId: telegramProcessId,
95
+ processBirthId: telegramQueueProcessBirthId,
96
+ manualFollowerOwnerId: telegramManualFollowerOwnerId,
97
+ getLeaderSocketPath: getTelegramBusSocketPath,
98
+ getFollowerSocketPath: getTelegramBusFollowerSocketPath,
99
+ } = busProcessRuntime;
100
+ const getTelegramBotId = Config.createTelegramConfigBotIdGetter(configStore);
101
+ const workspaceAdmissionRuntime =
102
+ WorkspaceAdmission.createTelegramWorkspaceAdmissionRuntimeBinding({
103
+ getProfileName: configStore.getActiveProfileName,
104
+ getBotToken: configStore.getBotToken,
105
+ getPath: Paths.resolveTelegramWorkspaceAdmissionPath,
106
+ owner: {
107
+ processId: telegramProcessId,
108
+ processBirthId: telegramQueueProcessBirthId,
109
+ },
110
+ });
111
+ const telegramWorkspaceOperationRuntime =
112
+ WorkspaceRetirement.createTelegramWorkspaceOperationRuntime({
113
+ getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
114
+ onReleaseError(error, operationKind) {
115
+ recordRuntimeEvent("bus", error, {
116
+ phase: "workspace-admission-release",
117
+ operationKind,
118
+ });
119
+ },
120
+ });
121
+ const getTelegramActiveProfileKey =
122
+ Config.createTelegramActiveProfileKeyGetter(configStore);
123
+ const getTelegramManualFollowerProfileKey =
124
+ BusFollower.createTelegramManualFollowerProfileKeyResolver({
125
+ getActiveProfileName: configStore.getActiveProfileName,
126
+ manualFollowerOwnerId: telegramManualFollowerOwnerId,
127
+ });
128
+ const telegramBusAuthSecret = Bus.createTelegramBusAuthSecret();
129
+ const telegramBusFollowerControlState =
130
+ BusFollower.createTelegramBusFollowerControlState();
131
+ const telegramBusFollowerRegistry = Bus.createTelegramBusFollowerRegistry();
132
+ const modelContextAvailabilityBinding =
133
+ Prompts.createTelegramModelContextAvailabilityBinding();
134
+ const telegramBusFollowerRegistrationState =
135
+ BusFollower.createTelegramBusFollowerRegistrationState({
136
+ onAvailabilityChanged: modelContextAvailabilityBinding.reconcile,
137
+ });
138
+ const telegramBusLeaderState =
139
+ Threads.createTelegramLeaderThreadStateRuntime();
140
+ const telegramThreadCapabilityState =
141
+ Polling.createTelegramThreadCapabilityStateRuntime();
142
+ const telegramProvisioningActivity =
143
+ Sync.createTelegramProvisioningActivityRuntime();
144
+ const messageOwnershipRuntime =
145
+ Ownership.createTelegramBusMessageOwnershipRuntime({
146
+ instanceId: telegramInstanceId,
147
+ getProfileKey: getTelegramActiveProfileKey,
148
+ listFollowers: telegramBusFollowerRegistry.list,
149
+ });
150
+ const { abort, lifecycle, queue, setup, typing } = bridgeRuntime;
151
+ const getTelegramUpdateAdmissionScope =
152
+ Journal.createTelegramUpdateJournalReceiptScopeResolver({
153
+ getProfileName: configStore.getActiveProfileName,
154
+ getBotToken: configStore.getBotToken,
155
+ getBotId: getTelegramBotId,
156
+ });
157
+ const telegramJournalBindingRuntime =
158
+ Journal.createTelegramUpdateJournalBindingRuntime({
159
+ base: {
160
+ getProfileName: configStore.getActiveProfileName,
161
+ getBotToken: configStore.getBotToken,
162
+ getBotId: getTelegramBotId,
163
+ onRecovery(event) {
164
+ recordRuntimeEvent(
165
+ "recovery",
166
+ event.kind === "repaired"
167
+ ? "Telegram update journal was repaired automatically."
168
+ : "Telegram update journal was reset after its damaged files were quarantined.",
169
+ {
170
+ phase: "journal-auto-recovery",
171
+ recoveryKind: event.kind,
172
+ journalPath: event.path,
173
+ revision: event.revision,
174
+ quarantinePath: event.quarantinePath,
175
+ reason: event.reason,
176
+ },
177
+ );
178
+ },
179
+ getQueueRuntimeIdentity() {
180
+ return {
181
+ instanceId: telegramInstanceId,
182
+ processId: telegramProcessId,
183
+ processBirthId: telegramQueueProcessBirthId,
184
+ };
185
+ },
186
+ getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
187
+ },
188
+ getLeaderJournalPath: Paths.resolveTelegramUpdateJournalPath,
189
+ getFollowerJournalPath(bindingKey, profileName) {
190
+ return Paths.resolveTelegramFollowerJournalPath(
191
+ bindingKey,
192
+ undefined,
193
+ profileName,
194
+ );
195
+ },
196
+ getActiveFollowerBindingKey: getTelegramManualFollowerProfileKey,
197
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
198
+ });
199
+ const telegramJournalReferenceRegistry =
200
+ Journal.createTelegramUpdateJournalReferenceRegistry();
201
+ const resolveTelegramUpdateJournalBinding =
202
+ telegramJournalBindingRuntime.resolveLeader;
203
+ const resolveTelegramFollowerJournalBinding =
204
+ telegramJournalBindingRuntime.resolveFollower;
205
+ const getTelegramQueueJournalBinding =
206
+ telegramJournalBindingRuntime.getActiveRecoveryKey;
207
+ const isTelegramBusRuntimeEnabled =
208
+ telegramThreadCapabilityState.isBusRuntimeEnabled;
209
+ Config.bindGlobalTelegramConfigRuntime(configStore);
210
+ const configControls = Config.createTelegramConfigControls(configStore);
211
+ const lockRuntime = Locks.createTelegramLockRuntime<Pi.ExtensionContext>({
212
+ key: Locks.createTelegramLockKeyResolver(configStore),
213
+ instanceId: telegramInstanceId,
214
+ busSecret: telegramBusAuthSecret,
215
+ staleHeartbeatMs: Locks.TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS,
216
+ });
217
+ const threadStore = Threads.createTelegramTopicTargetStore({
218
+ path: function () {
219
+ return Threads.getTelegramTopicTargetsPath(
220
+ undefined,
221
+ configStore.getActiveProfileName(),
222
+ );
223
+ },
224
+ telegramProfile: function () {
225
+ return configStore.getActiveProfileName();
226
+ },
227
+ canPersist: lockRuntime.owns,
228
+ commitPersist: lockRuntime.commitIfOwned,
229
+ getExternalReservedSlots: function () {
230
+ return workspaceAdmissionRuntime.resolve()?.listReservedSlots() ?? [];
231
+ },
232
+ });
233
+ runtimeDiagnostics.bindStorage({
234
+ getBotToken: configStore.getBotToken,
235
+ getProfileName: configStore.getActiveProfileName,
236
+ canReset: lockRuntime.owns,
237
+ commitReset: lockRuntime.commitIfOwned,
238
+ });
239
+ const lockOwnershipGuard =
240
+ Locks.createTelegramLockOwnershipGuard(lockRuntime);
241
+ const getCurrentLeaderEpoch = lockRuntime.getOwnedLeaderEpoch;
242
+ const telegramSessionContextStore =
243
+ Lifecycle.createTelegramSessionContextStore<Pi.ExtensionContext>({
244
+ getIdentity(ctx) {
245
+ return ctx.sessionManager ?? ctx.cwd;
246
+ },
247
+ });
248
+ const ownsTelegramDirectDelivery =
249
+ Locks.createTelegramDirectDeliveryOwnershipChecker({
250
+ lock: lockRuntime,
251
+ contextStore: telegramSessionContextStore,
252
+ });
253
+ const modelContextAvailabilityRuntime =
254
+ Prompts.createTelegramModelContextAvailabilityRuntime({
255
+ getActiveTools,
256
+ setActiveTools,
257
+ isAvailable() {
258
+ return (
259
+ ownsTelegramDirectDelivery() ||
260
+ telegramBusFollowerRegistrationState.isRegistered()
261
+ );
262
+ },
263
+ canReconcile() {
264
+ const ctx = telegramSessionContextStore.get();
265
+ return !ctx || Pi.isExtensionContextIdle(ctx);
266
+ },
267
+ });
268
+ modelContextAvailabilityBinding.bind(modelContextAvailabilityRuntime);
269
+ const activeTurnRuntime = Queue.createTelegramActiveTurnStore();
270
+ const proactivePushTargetGetter =
271
+ Config.createTelegramProactivePushTargetGetter({
272
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
273
+ getAssignedTarget() {
274
+ return (
275
+ telegramBusFollowerRegistrationState.getTarget() ??
276
+ telegramBusLeaderState.getTarget()
277
+ );
278
+ },
279
+ getAllowedUserId: configStore.getAllowedUserId,
280
+ });
281
+ const proactivePushChatIdGetter =
282
+ Config.createTelegramProactivePushChatIdGetter(proactivePushTargetGetter);
283
+ const buttonActionStore = Outbound.createTelegramButtonActionStore();
284
+ const planGenerativeAppOutput =
285
+ Outbound.createTelegramOutboundReplyPlanner(
286
+ buttonActionStore,
287
+ configControls.getAssistantRenderingMode,
288
+ );
289
+ const pendingModelSwitchStore =
290
+ Model.createPendingModelSwitchStore<
291
+ Model.ScopedTelegramModel<ActivePiModel>
292
+ >();
293
+ const modelMenuRuntime = Menu.createTelegramModelMenuRuntime<ActivePiModel>();
294
+ const sectionRegistry = Sections.createAndBindTelegramSectionRegistry();
295
+
296
+ const timeInjectionRuntime = TimeInjection.createTimeInjectionRuntime({
297
+ getConfig: Config.createTelegramTimeConfigGetter(configStore),
298
+ recordRuntimeEvent,
299
+ });
300
+ Outbound.bindTelegramRuntimeEventRecorder(recordRuntimeEvent);
301
+ const getContextModel = Pi.getExtensionContextModel;
302
+ const isIdle = Pi.isExtensionContextIdle;
303
+ const hasPendingMessages = Pi.hasExtensionContextPendingMessages;
304
+ const compact = Pi.compactExtensionContext;
305
+ const mediaGroupRuntime = Media.createTelegramMediaGroupController<
306
+ TelegramApi.TelegramMessage,
307
+ Pi.ExtensionContext
308
+ >();
309
+ const textGroupRuntime = TextGroups.createTelegramTextGroupController<
310
+ TelegramApi.TelegramMessage,
311
+ Pi.ExtensionContext
312
+ >();
313
+ const rawTelegramQueueStore =
314
+ Queue.createTelegramQueueStore<Pi.ExtensionContext>();
315
+ const telegramTransportStampRuntime =
316
+ Queue.createTelegramTransportStampRuntime({
317
+ getProfileName: configStore.getActiveProfileName,
318
+ getBotToken: configStore.getBotToken,
319
+ });
320
+ const telegramQueueStore = Queue.createTelegramTransportStampedQueueStore(
321
+ rawTelegramQueueStore,
322
+ telegramTransportStampRuntime.getStamp,
323
+ );
324
+ const telegramApiTargetActivityRuntime =
325
+ TelegramApi.createTelegramApiTargetActivityRuntime();
326
+ const captureWorkspaceExternalProtection =
327
+ WorkspaceRetirement.createTelegramWorkspaceExternalProtectionCapture({
328
+ listFollowers: telegramBusFollowerRegistry.list,
329
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
330
+ getQueuedItems: telegramQueueStore.getQueuedItems,
331
+ resolveLeaderJournal: resolveTelegramUpdateJournalBinding,
332
+ createFollowerJournalResolver:
333
+ telegramJournalBindingRuntime.createRecipientResolver,
334
+ withJournalReference(binding, operation) {
335
+ if (!binding.recoveryKey) throw new Error(
336
+ "Telegram workspace journal reference identity is unavailable.",
337
+ );
338
+ return telegramJournalReferenceRegistry.withReference({
339
+ referenceClass: "workspace-retirement",
340
+ recoveryKey: binding.recoveryKey,
341
+ }, operation);
342
+ },
343
+ discoverFollowerJournals() {
344
+ return Journal.discoverTelegramFollowerJournalPaths({
345
+ directory: Paths.resolveTelegramTempDir(),
346
+ profileName: configStore.getActiveProfileName(),
347
+ });
348
+ },
349
+ createJournalPathResolver: telegramJournalBindingRuntime.createPathResolver,
350
+ getJournalWriterProtection(journalBindingKey) {
351
+ const owner = Threads.getTelegramThreadOwnerFromProfileKey(
352
+ journalBindingKey,
353
+ );
354
+ if (owner.kind !== "manual-follower") return "unknown";
355
+ const liveness = Bus.getTelegramProcessBirthIdentityLiveness(
356
+ owner.instanceId,
357
+ );
358
+ return liveness === "alive"
359
+ ? "protected"
360
+ : liveness === "dead" ? "clear" : "unknown";
361
+ },
362
+ getDeliveryAuthorityProtection(binding) {
363
+ return telegramApiTargetActivityRuntime.hasPendingTarget(binding.target)
364
+ ? "protected"
365
+ : "clear";
366
+ },
367
+ });
368
+ const inactiveThreadCleanupReviewRuntime =
369
+ ThreadCleanupManager.createTelegramInactiveThreadCleanupReviewRuntime({
370
+ getProfileName() { return configStore.getActiveProfileName() ?? "default"; },
371
+ listBindings: threadStore.listWorkspaceBindings,
372
+ getProtection: captureWorkspaceExternalProtection,
373
+ listReservations: threadStore.listReservations,
374
+ listPendingProvisions: threadStore.listPendingProvisions,
375
+ listPendingCleanups: threadStore.listPendingCleanups,
376
+ getWorkStore() {
377
+ const profileName = configStore.getActiveProfileName() ?? "default";
378
+ const botToken = configStore.getBotToken();
379
+ if (!botToken) throw new Error("Telegram Thread cleanup review requires an active bot token.");
380
+ return ThreadCleanupManager.createTelegramThreadCleanupWorkStore({
381
+ path: Paths.resolveTelegramThreadCleanupWorkPath(undefined, profileName),
382
+ profileName,
383
+ tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
384
+ });
385
+ },
386
+ runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
387
+ });
388
+ const updateAdmissionRuntimeBinding =
389
+ Updates.createTelegramUpdateAdmissionRuntimeBinding<Pi.ExtensionContext>({
390
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
391
+ });
392
+ const deferredQueueDispatchRuntime =
393
+ Queue.createTelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>({
394
+ recordRuntimeEvent,
395
+ });
396
+ const pollingControllerState = Polling.createTelegramPollingControllerState();
397
+ const telegramSyncStateRuntime = Sync.createTelegramSyncStateRuntime();
398
+ const threadReconciliationRuntime =
399
+ ThreadReconciler.createThreadReconciliationRuntime({
400
+ recordRuntimeEvent,
401
+ scheduleSnapshotPersist: runtimeDiagnostics.scheduleSnapshotPersist,
402
+ });
403
+ const recordThreadReconciliationPlan = threadReconciliationRuntime.recordPlan;
404
+ const persistTelegramConfigWithSync =
405
+ Sync.createTelegramConfigSyncPersister<Config.TelegramConfig>({
406
+ persist: configStore.persist,
407
+ markConfigChange: telegramSyncStateRuntime.markConfigChange,
408
+ });
409
+ const {
410
+ current: currentInstanceThreadRuntime,
411
+ status: threadStatusProjectionRuntime,
412
+ getDisplayTitle: getThreadDisplayTitle,
413
+ } = Threads.createTelegramCurrentThreadAssembly({
414
+ instanceId: telegramInstanceId,
415
+ listRecords: threadStore.list,
416
+ listWorkspaceBindings: threadStore.listWorkspaceBindings,
417
+ getFollowerDisplayTitle: telegramBusFollowerRegistrationState.getDisplayTitle,
418
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
419
+ getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
420
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
421
+ getFollowerSlot: telegramBusFollowerRegistrationState.getSlot,
422
+ getFollowerThreadName: telegramBusFollowerRegistrationState.getThreadName,
423
+ getLeaderIdentity: telegramBusLeaderState.getIdentity,
424
+ getLeaderTarget: telegramBusLeaderState.getTarget,
425
+ getLeaderProtocol: telegramBusFollowerRegistrationState.getLeaderProtocol,
426
+ status: {
427
+ getThreadMode: function () {
428
+ return threadStore.getBotState().threadMode;
429
+ },
430
+ isBusPollingStarted: telegramThreadCapabilityState.isBusPollingStarted,
431
+ listFollowers: telegramBusFollowerRegistry.list,
432
+ listReservations: threadStore.listReservations,
433
+ listSyncObservations: threadStore.listSyncObservations,
434
+ getLeaderSocketPath: getTelegramBusSocketPath,
435
+ getFollowerSocketPath: getTelegramBusFollowerSocketPath,
436
+ getTransportKind: BusTransport.getTelegramBusTransportKind,
437
+ },
438
+ });
439
+ const findCurrentThreadRecord = currentInstanceThreadRuntime.findRecord;
440
+ const getCurrentInstanceThreadIdentity =
441
+ currentInstanceThreadRuntime.getIdentity;
442
+ const statusRuntime = Status.createTelegramBridgeStatusRuntime<
443
+ Pi.ExtensionContext,
444
+ Queue.TelegramQueueItem<Pi.ExtensionContext>
445
+ >({
446
+ getConfig: Status.createTelegramBridgeStatusConfigGetter(configStore),
447
+ getActiveProfileName: configStore.getActiveProfileName,
448
+ getDiagnosticPaths: Paths.getTelegramDiagnosticsDisplayPaths,
449
+ isPollingActive: Polling.createTelegramPollingActivityReader(
450
+ pollingControllerState,
451
+ ),
452
+ getPollingState: Polling.createTelegramPollingStateReader(
453
+ pollingControllerState,
454
+ ),
455
+ getInboundWorkerState() {
456
+ return updateAdmissionRuntimeBinding.getActive()?.getState();
457
+ },
458
+ getAcceptedThroughUpdateId() {
459
+ return Journal.withTelegramResolvedUpdateJournalReference({
460
+ registry: telegramJournalReferenceRegistry,
461
+ resolveBinding: resolveTelegramUpdateJournalBinding,
462
+ referenceClass: "polling-cursor",
463
+ operation(binding) { return binding.journal.read().acceptedThroughUpdateId; },
464
+ });
465
+ },
466
+ getActiveSourceMessageIds: activeTurnRuntime.getSourceMessageIds,
467
+ hasActiveTurn: activeTurnRuntime.has,
468
+ hasDispatchPending: lifecycle.hasDispatchPending,
469
+ isCompactionInProgress: lifecycle.isCompactionInProgress,
470
+ getActiveToolExecutions: lifecycle.getActiveToolExecutions,
471
+ hasPendingModelSwitch: pendingModelSwitchStore.has,
472
+ getQueuedItems: telegramQueueStore.getQueuedItems,
473
+ getQueuedItemCount: Queue.countExecutableTelegramQueueItems,
474
+ formatQueuedStatus: Queue.formatQueuedTelegramItemsStatus,
475
+ getRecentRuntimeEvents: runtimeEvents.getEvents,
476
+ getRuntimeLockState: lockRuntime.getStatusLabel,
477
+ ...threadStatusProjectionRuntime,
478
+ getBusProtocol() {
479
+ return telegramBusProtocolIdentity;
480
+ },
481
+ getBusLifecyclePhase: telegramBusFollowerControlState.getLifecyclePhase,
482
+ getBotThreadMode() {
483
+ return threadStore.getBotState();
484
+ },
485
+ getSyncState: telegramSyncStateRuntime.getState,
486
+ getThreadReconciliationState() {
487
+ return threadReconciliationRuntime.getState();
488
+ },
489
+ });
490
+ runtimeDiagnostics.bindStatus({
491
+ instanceId: telegramInstanceId,
492
+ updateStatus: statusRuntime.updateStatus,
493
+ getStatusState: statusRuntime.getStatusState,
494
+ async persistSnapshot(snapshot) {
495
+ threadStore.setStatusSnapshot(snapshot);
496
+ await threadStore.persist();
497
+ },
498
+ });
499
+ const updateStatus = runtimeDiagnostics.updateStatus;
500
+ const getStatusLines = runtimeDiagnostics.getStatusLines;
501
+ const inboundHandlerRuntime = Inbound.createTelegramInboundHandlerRuntime({
502
+ getHandlers: configStore.getInboundHandlers,
503
+ execCommand: CommandTemplates.execCommandTemplate,
504
+ getCwd: Pi.getExtensionContextCwd,
505
+ recordRuntimeEvent,
506
+ });
507
+
508
+ // --- Telegram API ---
509
+
510
+ const directTelegramApiRuntime =
511
+ TelegramApi.createDefaultTelegramBridgeApiRuntime({
512
+ getBotToken: configStore.getBotToken,
513
+ recordRuntimeEvent,
514
+ targetActivity: telegramApiTargetActivityRuntime,
515
+ workspaceAdmission: workspaceAdmissionRuntime.resolve,
516
+ captureRequestErrorHandler(body) {
517
+ return Sync.captureTelegramStaleTargetRequestRecovery(body, {
518
+ ...staleTopicApiErrorRecoveryDeps,
519
+ getCurrentLeaderEpoch,
520
+ getSessionGeneration: telegramSessionContextStore.getGeneration,
521
+ getProfileName: configStore.getActiveProfileName,
522
+ onRecovered: runtimeDiagnostics.scheduleSnapshotPersist,
523
+ });
524
+ },
525
+ });
526
+ const telegramBusFollowerClients =
527
+ BusFollower.createTelegramBusFollowerClientRuntime<
528
+ Pi.ExtensionContext,
529
+ Updates.TelegramMessageReactionUpdated,
530
+ Routing.TelegramRoutedCallbackQuery,
531
+ Routing.TelegramRoutedMessage
532
+ >({
533
+ socketPath: getTelegramBusSocketPath,
534
+ instanceId: telegramInstanceId,
535
+ getApiAuthSecret: telegramBusFollowerControlState.getActiveAuthSecret,
536
+ getForwardingAuthSecret() {
537
+ return telegramBusAuthSecret;
538
+ },
539
+ getRegistrationGeneration:
540
+ telegramBusFollowerRegistrationState.getGeneration,
541
+ waitForRegistrationGeneration:
542
+ telegramBusFollowerRegistrationState.waitForGeneration,
543
+ getForwardCommentBatchPosition:
544
+ textGroupRuntime.getPreparedForwardingPosition,
545
+ validateForwardOwnership:
546
+ Bus.createTelegramBusForwardOwnershipValidator(telegramBusFollowerRegistry),
547
+ recordRuntimeEvent,
548
+ });
549
+ const telegramApiRuntime = BusApi.createTelegramBusAwareApiRuntime({
550
+ directRuntime: directTelegramApiRuntime,
551
+ ownsDirect() {
552
+ return lockRuntime.owns();
553
+ },
554
+ getDefaultTarget: proactivePushTargetGetter,
555
+ callFollowerApi: telegramBusFollowerClients.callApi,
556
+ });
557
+ const {
558
+ call: callTelegramApi,
559
+ callMultipart,
560
+ deleteWebhook,
561
+ getUpdates,
562
+ setMyCommands,
563
+ sendTypingAction,
564
+ sendChatAction,
565
+ sendRecordVoiceAction,
566
+ sendMessageDraft,
567
+ sendMessage,
568
+ sendRichMessage,
569
+ sendRichMessageDraft,
570
+ downloadFile: downloadTelegramBridgeFile,
571
+ editMessageText: editTelegramMessageText,
572
+ editMessageReplyMarkup: editTelegramMessageReplyMarkup,
573
+ answerCallbackQuery,
574
+ answerGuestQuery,
575
+ deleteMessage: deleteTelegramMessage,
576
+ prepareTempDir,
577
+ } = telegramApiRuntime;
578
+
579
+ // --- Message Delivery ---
580
+
581
+ const sendGuestReply = Replies.createGuestMarkdownReplySender({
582
+ answerGuestQuery,
583
+ });
584
+
585
+ // Answer guest queries immediately and replace the ACK with the final text.
586
+ const answerGuestQueryForInlineMessage =
587
+ telegramApiRuntime.answerGuestQueryForInlineMessage;
588
+ const editGuestReply = Replies.createGuestMarkdownReplyEditor({
589
+ editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
590
+ });
591
+
592
+ const promptDispatchRuntime = Runtime.createTelegramPromptDispatchRuntime({
593
+ lifecycle,
594
+ typing,
595
+ getDefaultChatId: proactivePushChatIdGetter,
596
+ sendTypingAction,
597
+ sendAggregateTypingAction:
598
+ BusApi.createTelegramAggregateTypingActionSender(telegramApiRuntime),
599
+ updateStatus,
600
+ isContextActive: telegramSessionContextStore.isCurrent,
601
+ getTransportAuthority() {
602
+ if (ownsTelegramDirectDelivery()) {
603
+ const epoch = getCurrentLeaderEpoch();
604
+ return epoch === undefined ? undefined : `direct:${epoch}`;
605
+ }
606
+ if (!telegramBusFollowerRegistrationState.isRegistered()) return undefined;
607
+ const generation = telegramBusFollowerRegistrationState.getGeneration();
608
+ return generation ? `follower:${generation}` : undefined;
609
+ },
610
+ recordRuntimeEvent,
611
+ });
612
+ const currentModelRuntime = Model.createCurrentModelRuntime({
613
+ getContextModel,
614
+ updateStatus,
615
+ });
616
+
617
+ // --- Reply Runtime & Preview ---
618
+
619
+ const replyRuntime = Replies.createTelegramRenderedMessageDeliveryRuntime({
620
+ recordOwnership: messageOwnershipRuntime.recordLocal,
621
+ sendMessage,
622
+ sendRichMessage,
623
+ getAssistantRenderingMode: configControls.getAssistantRenderingMode,
624
+ editMessage: editTelegramMessageText,
625
+ });
626
+ const { replyTransport, editInteractiveMessage, sendInteractiveMessage } =
627
+ replyRuntime;
628
+ const deliveryTargetPolicyRuntime =
629
+ Delivery.createTelegramDeliveryTargetPolicyRuntime({
630
+ ownsDirect: lockRuntime.owns,
631
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
632
+ getAllowedChatId: configStore.getAllowedUserId,
633
+ getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
634
+ getLeaderTarget: telegramBusLeaderState.getTarget,
635
+ listThreadRecords: threadStore.list,
636
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
637
+ getActiveGuestQueryId: activeTurnRuntime.getGuestQueryId,
638
+ });
639
+ const deliveryGenerationSeed =
640
+ Delivery.createTelegramDeliveryGenerationSeed(telegramInstanceId);
641
+ const deliveryLifecycleRuntime =
642
+ Delivery.createTelegramBridgeDeliveryLifecycleHooks({
643
+ generationSeed: deliveryGenerationSeed,
644
+ getTargetPolicyView: deliveryTargetPolicyRuntime.getTargetPolicyView,
645
+ getTransportStamp: telegramTransportStampRuntime.getStamp,
646
+ isTransportStampActive: telegramTransportStampRuntime.isActive,
647
+ getActiveTurnTarget: deliveryTargetPolicyRuntime.getActiveTurnTarget,
648
+ api: telegramApiRuntime,
649
+ recordOwnership: messageOwnershipRuntime.recordLocal,
650
+ recordFailure(operation, error, target) {
651
+ recordRuntimeEvent("delivery", error, {
652
+ operation,
653
+ scope: target?.threadId === undefined ? "aggregate" : "thread",
654
+ });
655
+ },
656
+ });
657
+ const { sendTextReply, sendMarkdownReply } =
658
+ Outbound.createTelegramOutboundTextReplyRuntime({
659
+ sendTextReply: replyRuntime.sendTextReply,
660
+ sendMarkdownReply: replyRuntime.sendMarkdownReply,
661
+ execCommand: CommandTemplates.execCommandTemplate,
662
+ getHandlers: configStore.getOutboundHandlers,
663
+ recordRuntimeEvent,
664
+ });
665
+ const invokeGenerativeAppBoundButtonAction =
666
+ Bindings.createTelegramGenerativeAppBoundButtonActionInvoker({
667
+ agentDir: Paths.resolveAgentDir(),
668
+ assertExecutionCurrent: Updates.assertTelegramUpdateExecutionCurrent,
669
+ getExecutionFence: Updates.getTelegramUpdateExecutionFence,
670
+ planOutput: planGenerativeAppOutput,
671
+ sendMarkdownReply,
672
+ editInteractiveMessage,
673
+ recordRuntimeEvent,
674
+ });
675
+ const nativeMarkdownDraftSender =
676
+ TelegramApi.createTelegramAssistantDraftSender({
677
+ getAssistantRenderingMode: configControls.getAssistantRenderingMode,
678
+ renderMarkdownToHtmlDraft: Replies.renderTelegramMarkdownToHtmlDraft,
679
+ sendMessageDraft,
680
+ sendRichMessageDraft,
681
+ });
682
+ const previewRuntime = Preview.createTelegramAssistantPreviewRuntime({
683
+ getActiveTurn: activeTurnRuntime.get,
684
+ isAssistantMessage: Replies.isAssistantAgentMessage,
685
+ getMessageText: Replies.getAgentMessageText,
686
+ getDefaultReplyToMessageId: activeTurnRuntime.getReplyToMessageId,
687
+ sendDraft: nativeMarkdownDraftSender,
688
+ canSend: configControls.areDraftPreviewsEnabled,
689
+ sendMarkdownReply,
690
+ recordRuntimeEvent,
691
+ ...replyTransport,
692
+ });
693
+ const {
694
+ activityRuntime,
695
+ activityVerbosityRuntime,
696
+ assistantOutputRuntime,
697
+ publicationRuntime,
698
+ } = Bindings.createTelegramActivityBindingRuntime({
699
+ generation: deliveryGenerationSeed,
700
+ assistantOutput: {
701
+ prepareTelegramPreview: previewRuntime.preparePublication,
702
+ authority: {
703
+ getPreferredTarget: proactivePushTargetGetter,
704
+ getFallbackChatId: proactivePushChatIdGetter,
705
+ getTransportStamp: telegramTransportStampRuntime.getStamp,
706
+ isTransportStampActive: telegramTransportStampRuntime.isActive,
707
+ ownsDirect: lockRuntime.owns,
708
+ getDirectEpoch: lockRuntime.getOwnedLeaderEpoch,
709
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
710
+ getFollowerGeneration:
711
+ telegramBusFollowerRegistrationState.getGeneration,
712
+ },
713
+ sender: {
714
+ recordOwnership: messageOwnershipRuntime.recordLocal,
715
+ sendMessage,
716
+ sendRichMessage,
717
+ editMessage: editTelegramMessageText,
718
+ getAssistantRenderingMode: configControls.getAssistantRenderingMode,
719
+ planButtonReply:
720
+ Outbound.createTelegramButtonReplyPlanner(buttonActionStore),
721
+ execCommand: CommandTemplates.execCommandTemplate,
722
+ getHandlers: configStore.getOutboundHandlers,
723
+ recordRuntimeEvent,
724
+ },
725
+ recordRuntimeEvent,
726
+ },
727
+ activityVerbosity: {
728
+ getActivityMode: configControls.getActivityVerbosity,
729
+ refreshActivityMode: configControls.refreshActivityVerbosity,
730
+ resolveTarget(event) {
731
+ return event.target ?? proactivePushTargetGetter();
732
+ },
733
+ sendMessage,
734
+ sendRichMessage,
735
+ editMessageText: editTelegramMessageText,
736
+ },
737
+ });
738
+ const {
739
+ mutation: queueMutationRuntime,
740
+ dispatchNext: dispatchNextQueuedTelegramTurn,
741
+ watchdog: queueDispatchWatchdogRuntime,
742
+ } = Bindings.createTelegramQueueBindingRuntime({
743
+ store: telegramQueueStore,
744
+ queue,
745
+ lifecycle,
746
+ activeTurn: activeTurnRuntime,
747
+ admission: updateAdmissionRuntimeBinding,
748
+ transportStamp: telegramTransportStampRuntime,
749
+ deferredDispatch: deferredQueueDispatchRuntime,
750
+ promptDispatch: promptDispatchRuntime,
751
+ isIdle,
752
+ hasPendingMessages,
753
+ updateStatus,
754
+ sendTextReply,
755
+ sendUserMessage,
756
+ recordRuntimeEvent,
757
+ });
758
+ const { finalizeMarkdownPreview, preparePreviewDelivery } =
759
+ Outbound.createTelegramOutboundTextPreviewRuntime({
760
+ finalizeMarkdownPreview: previewRuntime.finalizeMarkdown,
761
+ preparePreviewDelivery: previewRuntime.prepareDelivery,
762
+ execCommand: CommandTemplates.execCommandTemplate,
763
+ getHandlers: configStore.getOutboundHandlers,
764
+ recordRuntimeEvent,
765
+ });
766
+
767
+ // --- Model And Menu Setup ---
768
+
769
+ const modelSwitchController =
770
+ Model.createTelegramModelSwitchControllerRuntime({
771
+ isIdle,
772
+ getPendingModelSwitch: pendingModelSwitchStore.get,
773
+ setPendingModelSwitch: pendingModelSwitchStore.set,
774
+ getActiveTurn: activeTurnRuntime.get,
775
+ getAbortHandler: abort.getHandler,
776
+ hasAbortHandler: abort.hasHandler,
777
+ getActiveToolExecutions: lifecycle.getActiveToolExecutions,
778
+ allocateItemOrder: queue.allocateItemOrder,
779
+ allocateControlOrder: queue.allocateControlOrder,
780
+ appendQueuedItem: queueMutationRuntime.append,
781
+ updateStatus,
782
+ });
783
+ const getQueueItemCount =
784
+ Queue.createTelegramQueueItemCountGetter(telegramQueueStore);
785
+ const getPromptTemplateCommands =
786
+ PromptTemplates.createTelegramPromptTemplateCommandGetter({
787
+ getCommands,
788
+ getReservedCommandNames: Commands.getTelegramReservedCommandNames,
789
+ });
790
+ const menuActions = Menu.createTelegramMenuActionRuntimeWithStateBuilder({
791
+ runtime: modelMenuRuntime,
792
+ createSettingsManager: Pi.createSettingsManager,
793
+ getActiveModel: currentModelRuntime.get,
794
+ getThinkingLevel,
795
+ getQueueItemCount,
796
+ buildStatusHtml: Commands.createTelegramAppMenuHtmlBuilder({
797
+ buildStatusHtml: Status.createTelegramStatusHtmlBuilder({
798
+ getActiveModel: currentModelRuntime.get,
799
+ isCompactionInProgress: lifecycle.isCompactionInProgress,
800
+ getBridgeStatusLineState: statusRuntime.getStatusState,
801
+ }),
802
+ getPromptTemplateCommands,
803
+ }),
804
+ storeModelMenuState: modelMenuRuntime.storeState,
805
+ isIdle,
806
+ canOfferInFlightModelSwitch: modelSwitchController.canOfferInFlightSwitch,
807
+ sendTextReply,
808
+ editInteractiveMessage,
809
+ sendInteractiveMessage,
810
+ sectionRegistry,
811
+
812
+ // Menu/status UI uses this to reflect whether the active Telegram turn expects voice delivery.
813
+ isVoiceReplyActive: function () {
814
+ const turn = activeTurnRuntime.get();
815
+ return Voice.isVoiceTurn(turn);
816
+ },
817
+ });
818
+
819
+ // --- Queue And Settings Menus ---
820
+
821
+ const getQueueMenuState = Menu.createTelegramModelMenuStateBuilder({
822
+ runtime: modelMenuRuntime,
823
+ createSettingsManager: Pi.createSettingsManager,
824
+ getActiveModel: currentModelRuntime.get,
825
+ });
826
+ const queueMenuRuntime = MenuQueue.createTelegramQueueMenuRuntime({
827
+ telegramQueueStore,
828
+ queueMutationRuntime,
829
+ sendInteractiveMessage,
830
+ editInteractiveMessage,
831
+ answerCallbackQuery,
832
+ getModelMenuState: getQueueMenuState,
833
+ getStoredModelMenuState: modelMenuRuntime.getState,
834
+ storeModelMenuState: modelMenuRuntime.storeState,
835
+ updateStatusMessage: menuActions.updateStatusMessage,
836
+ updateStatus,
837
+ });
838
+ const settingsMenuRuntime = MenuSettings.createTelegramSettingsMenuRuntime(
839
+ {
840
+ reloadConfig: configStore.load,
841
+ getModelMenuState: getQueueMenuState,
842
+ getStoredModelMenuState: modelMenuRuntime.getState,
843
+ storeModelMenuState: modelMenuRuntime.storeState,
844
+ editInteractiveMessage,
845
+ sendInteractiveMessage,
846
+ answerCallbackQuery,
847
+ ...configControls,
848
+ reviewInactiveThreads: inactiveThreadCleanupReviewRuntime.review,
849
+ getThreadDisplayMode() {
850
+ return threadStore.getBotState().threadMode === "enabled"
851
+ ? Config.resolveTelegramThreadDisplayMode(configStore.get()) : undefined;
852
+ },
853
+ async setThreadDisplayMode(mode) {
854
+ try {
855
+ await ThreadDisplay.applyTelegramThreadDisplaySetting(mode, {
856
+ getProfileKey: configStore.getActiveProfileName,
857
+ ownsLeader() { return getCurrentLeaderEpoch() !== undefined; },
858
+ getLeaderSetter() { return telegramBusLeaderRuntime.setThreadDisplayMode; },
859
+ getFollowerSetter() { return telegramBusFollowerRegistration.setThreadDisplayMode; },
860
+ reloadConfig: configStore.load,
861
+ });
862
+ } catch (error) {
863
+ recordRuntimeEvent("bus", error, { phase: "thread-display-setting" });
864
+ throw error;
865
+ }
866
+ },
867
+ },
868
+ sectionRegistry,
869
+ );
870
+
871
+ // --- Polling ---
872
+
873
+ const foreignOwnedUpdateForwarder =
874
+ telegramBusFollowerClients.foreignOwnedUpdateForwarder;
875
+ const followerTargetController = telegramBusFollowerClients.targetController;
876
+ const restoreFollowerThreadTarget =
877
+ Bus.createTelegramBusFollowerThreadRestoreHandler({
878
+ followerRegistry: telegramBusFollowerRegistry,
879
+ followerTargetController,
880
+ onRestored() {
881
+ telegramSyncStateRuntime.markSliceFresh(
882
+ Sync.TELEGRAM_SYNC_SLICE_TARGET_BINDINGS,
883
+ {
884
+ nowMs: Date.now(),
885
+ action: "follower-thread-restore",
886
+ },
887
+ );
888
+ },
889
+ });
890
+ const observedThreadTargetBinding =
891
+ Polling.createTelegramThreadTargetObservationBinding<Pi.ExtensionContext>();
892
+ const topicLifecycleSync =
893
+ Sync.createTelegramObservedTopicLifecycleSyncHandler({
894
+ topicTargetStore: threadStore,
895
+ isBusEnabled: isTelegramBusRuntimeEnabled,
896
+ callApi: callTelegramApi,
897
+ isTopicProvisioningActive: telegramProvisioningActivity.isActive,
898
+ getCurrentLeaderEpoch,
899
+ getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
900
+ recordThreadReconciliationPlan,
901
+ runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
902
+ assertExecutionCurrent(message) {
903
+ Updates.assertTelegramUpdateExecutionCurrent(message);
904
+ },
905
+ getSyncState: telegramSyncStateRuntime.getState,
906
+ setSyncState: telegramSyncStateRuntime.setState,
907
+ recordEvent: recordRuntimeEvent,
908
+ });
909
+ const inboundBusProjectionRuntime =
910
+ Routing.createTelegramInboundBusProjectionRuntime({
911
+ instanceId: telegramInstanceId,
912
+ listFollowers: telegramBusFollowerRegistry.list,
913
+ listThreadRecords: threadStore.list,
914
+ getLeaderTarget: telegramBusLeaderState.getTarget,
915
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
916
+ getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
917
+ getCurrentIdentity: getCurrentInstanceThreadIdentity,
918
+ });
919
+ const telegramThreadDisplayNameRenameBinding =
920
+ Commands.createTelegramThreadDisplayNameRenameBinding();
921
+ const telegramThreadDisplayNameResetBinding =
922
+ Commands.createTelegramThreadDisplayNameResetBinding();
923
+ const inboundRouteRuntime = Routing.createTelegramInboundRouteRuntime({
924
+ configStore,
925
+ callApi: callTelegramApi,
926
+ getCurrentInstanceId() {
927
+ return telegramInstanceId;
928
+ },
929
+ getAdmissionScope: getTelegramUpdateAdmissionScope,
930
+ getAdmissionJournalBinding: getTelegramQueueJournalBinding,
931
+ getMessageOwnership: messageOwnershipRuntime.store.get,
932
+ recordMessageOwnership: messageOwnershipRuntime.recordRouted,
933
+ ...inboundBusProjectionRuntime,
934
+ getDisplayTitle: getThreadDisplayTitle,
935
+ getCurrentLeaderEpoch,
936
+ setCurrentLeaderIdentity: telegramBusLeaderState.set,
937
+ getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
938
+ recordThreadReconciliationPlan,
939
+ handleTelegramTopicLifecycleUpdate: topicLifecycleSync,
940
+ handleTelegramThreadTargetObserved(_target, ctx) {
941
+ return observedThreadTargetBinding.handle(ctx);
942
+ },
943
+ foreignOwnedUpdateForwarder,
944
+ replaceFollowerThreadTarget: restoreFollowerThreadTarget,
945
+ bridgeRuntime,
946
+ activeTurnRuntime,
947
+ mediaGroupRuntime,
948
+ textGroupRuntime,
949
+ telegramQueueStore,
950
+ queueMutationRuntime,
951
+ modelMenuRuntime,
952
+ currentModelRuntime,
953
+ modelSwitchController,
954
+ menuActions,
955
+ updateSettingsMenuMessage: settingsMenuRuntime.updateSettingsMenuMessage,
956
+ openQueueMenu: queueMenuRuntime.openQueueMenu,
957
+ queueMenuCallbackHandler: queueMenuRuntime.handleCallbackQuery,
958
+ openSettingsMenu: settingsMenuRuntime.openSettingsMenu,
959
+ settingsMenuCallbackHandler: settingsMenuRuntime.handleCallbackQuery,
960
+ sectionRegistry,
961
+ buttonActionStore,
962
+ invokeBoundButtonAction: invokeGenerativeAppBoundButtonAction,
963
+ inboundHandlerRuntime,
964
+ threadStore,
965
+ runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
966
+ updateStatus,
967
+ isContextActive: telegramSessionContextStore.isCurrent,
968
+ dispatchNextQueuedTelegramTurn,
969
+ requestDeferredDispatchNextQueuedTelegramTurn:
970
+ deferredQueueDispatchRuntime.request,
971
+ hasDeferredDispatchContext: deferredQueueDispatchRuntime.isBound,
972
+ startTypingLoop: promptDispatchRuntime.startTypingLoop,
973
+ stopTypingLoop: typing.stop,
974
+ answerCallbackQuery,
975
+ editInteractiveMessage,
976
+ editMessageReplyMarkup: editTelegramMessageReplyMarkup,
977
+ sendInteractiveMessage,
978
+ deleteMessage: deleteTelegramMessage,
979
+ answerGuestQuery,
980
+ answerGuestQueryForInlineMessage,
981
+ sendTextReply,
982
+ setMyCommands,
983
+ validateThreadName(threadName) {
984
+ return Threads.getTelegramManualThreadDisplayNameValidationError(
985
+ threadName,
986
+ );
987
+ },
988
+ renameCurrentThread: telegramThreadDisplayNameRenameBinding.rename,
989
+ resetCurrentThreadName: telegramThreadDisplayNameResetBinding.reset,
990
+ getCommands,
991
+ downloadFile: downloadTelegramBridgeFile,
992
+ resolveTimeLine: timeInjectionRuntime.resolveLine,
993
+ getThinkingLevel,
994
+ setThinkingLevel,
995
+ persistScopedModelPatterns: Pi.createScopedModelPatternPersister({
996
+ createSettingsManager: Pi.createSettingsManager,
997
+ clearCachedModelMenuInputs: modelMenuRuntime.clearCachedInputs,
998
+ }),
999
+ setModel,
1000
+ sendUserMessage,
1001
+ isIdle,
1002
+ hasPendingMessages,
1003
+ compact,
1004
+ recordRuntimeEvent,
1005
+ });
1006
+ const queueHandoffReconciliationBinding =
1007
+ Updates.createTelegramQueueHandoffReconciliationBinding<Pi.ExtensionContext>(
1008
+ function (error) {
1009
+ recordRuntimeEvent("inbound-worker", error, {
1010
+ phase: "queue-handoff-reconcile",
1011
+ });
1012
+ },
1013
+ );
1014
+ const staleTopicApiErrorRecoveryDeps = {
1015
+ topicTargetStore: threadStore,
1016
+ getSyncState: telegramSyncStateRuntime.getState,
1017
+ setSyncState: telegramSyncStateRuntime.setState,
1018
+ recordEvent: recordRuntimeEvent,
1019
+ getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
1020
+ };
1021
+ const recoverStaleTelegramTopicApiError =
1022
+ Sync.createTelegramStaleTopicApiErrorRecoveryRuntime(
1023
+ staleTopicApiErrorRecoveryDeps,
1024
+ );
1025
+ const {
1026
+ owner: updateWorkerOwnerRuntime,
1027
+ leader: updateAdmissionLifecycleRuntime,
1028
+ follower: followerAdmissionLifecycleRuntime,
1029
+ } = Updates.createTelegramUpdateAdmissionRuntimeAssembly<
1030
+ Parameters<typeof inboundRouteRuntime.handleUpdate>[0] &
1031
+ Journal.TelegramJournaledUpdate,
1032
+ Pi.ExtensionContext
1033
+ >({
1034
+ runtimeBinding: updateAdmissionRuntimeBinding,
1035
+ acquireSourceReference(role, binding) {
1036
+ return telegramJournalReferenceRegistry.acquire({
1037
+ referenceClass: role === "leader" ? "leader-lifecycle" : "follower-lifecycle",
1038
+ recoveryKey: binding.recoveryKey,
1039
+ });
1040
+ },
1041
+ owner: {
1042
+ instanceId: telegramInstanceId,
1043
+ processId: telegramProcessId,
1044
+ processBirthId: telegramQueueProcessBirthId,
1045
+ getSessionGeneration: telegramSessionContextStore.getGeneration,
1046
+ isContextCurrent: telegramSessionContextStore.isCurrent,
1047
+ dispatchNext: dispatchNextQueuedTelegramTurn,
1048
+ requestQueueHandoffReconciliation:
1049
+ queueHandoffReconciliationBinding.request,
1050
+ },
1051
+ worker: {
1052
+ defaultHandle: inboundRouteRuntime.handleUpdate,
1053
+ onStateChange: runtimeDiagnostics.scheduleSnapshotPersist,
1054
+ settleTerminalExecutionFailure(error) {
1055
+ return Sync.settleStaleTelegramTopicExecutionFailure(
1056
+ error,
1057
+ staleTopicApiErrorRecoveryDeps,
1058
+ );
1059
+ },
1060
+ },
1061
+ leader: {
1062
+ resolveBinding: resolveTelegramUpdateJournalBinding,
1063
+ hasAuthority: lockRuntime.owns,
1064
+ },
1065
+ follower: {
1066
+ resolveBinding: resolveTelegramFollowerJournalBinding,
1067
+ isRegistered: telegramBusFollowerRegistrationState.isRegistered,
1068
+ getGeneration: telegramBusFollowerRegistrationState.getGeneration,
1069
+ prepareUpdateForExecution(update) {
1070
+ return BusFollower.prepareTelegramBusFollowerJournaledUpdateForExecution(
1071
+ update,
1072
+ textGroupRuntime.prepareForwardedMessage,
1073
+ );
1074
+ },
1075
+ },
1076
+ recordRuntimeEvent,
1077
+ });
1078
+ const queueHandoffStagingRuntime =
1079
+ Queue.createTelegramQueueHandoffStagingRuntime({
1080
+ liveStore: telegramQueueStore,
1081
+ createControlExecution:
1082
+ Updates.createTelegramQueueHandoffControlExecutionFactory({
1083
+ isContextCurrent: telegramSessionContextStore.isCurrent,
1084
+ showStatus: menuActions.sendStatusMessage,
1085
+ openModelMenu: menuActions.openModelMenu,
1086
+ }),
1087
+ });
1088
+ const acceptStagedQueueHandoff =
1089
+ Updates.createTelegramQueueHandoffRecipientRuntime({
1090
+ staging: queueHandoffStagingRuntime,
1091
+ getRecipientOwner: updateWorkerOwnerRuntime.getQueueOwnerIdentity,
1092
+ getLifecycleForBinding:
1093
+ updateAdmissionRuntimeBinding.getLifecycleForJournalBinding,
1094
+ isTransportStampActive: telegramTransportStampRuntime.isActive,
1095
+ dispatchNext: dispatchNextQueuedTelegramTurn,
1096
+ });
1097
+ const followerDurableAdmissionRuntime =
1098
+ BusFollower.createTelegramBusFollowerDurableAdmissionRuntime({
1099
+ journal: {
1100
+ appendBatch(updates) {
1101
+ return followerAdmissionLifecycleRuntime.appendBatch(updates);
1102
+ },
1103
+ },
1104
+ signalWorker() {
1105
+ followerAdmissionLifecycleRuntime.signal();
1106
+ },
1107
+ });
1108
+ const promoteTelegramBusFollowerToLeader: BusFollower.TelegramBusFollowerPromotionHandler<Pi.ExtensionContext> =
1109
+ BusFollower.createTelegramBusFollowerPromotionHandler<Pi.ExtensionContext>({
1110
+ topicTargetStore: threadStore,
1111
+ instanceId: telegramInstanceId,
1112
+ getActiveProfileName: configStore.getActiveProfileName,
1113
+ getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
1114
+ async startLeader(ctx, election, onAcquired): Promise<boolean> {
1115
+ const result = await lockedPollingRuntime.start(ctx, {
1116
+ election,
1117
+ onAcquired,
1118
+ });
1119
+ return result.ok;
1120
+ },
1121
+ recordRuntimeEvent,
1122
+ });
1123
+ const agentMessageRuntime = AgentMessages.createTelegramAgentMessageRuntime({
1124
+ instanceId: telegramInstanceId,
1125
+ getAllowedChatId: configStore.getAllowedUserId,
1126
+ getLeaderTarget: telegramBusLeaderState.getTarget,
1127
+ getLeaderThreadName() {
1128
+ return findCurrentThreadRecord()?.threadName;
1129
+ },
1130
+ followerRegistry: telegramBusFollowerRegistry,
1131
+ getDisplayTitle: getThreadDisplayTitle,
1132
+ getContext: telegramSessionContextStore.get,
1133
+ handleUpdate: inboundRouteRuntime.handleUpdate,
1134
+ });
1135
+ const agentMessageToolRoutingRuntime =
1136
+ Bindings.createTelegramAgentMessageToolRoutingRuntime({
1137
+ ownsLeader: lockRuntime.owns,
1138
+ ownsDirectDelivery: ownsTelegramDirectDelivery,
1139
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
1140
+ getSourceTarget: proactivePushTargetGetter,
1141
+ getSourceThreadName() {
1142
+ return findCurrentThreadRecord()?.threadName;
1143
+ },
1144
+ local: agentMessageRuntime,
1145
+ follower: telegramBusFollowerClients.agentMessages,
1146
+ });
1147
+ const telegramBusFollowerAssembly: BusFollower.TelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext> =
1148
+ BusFollower.createTelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext>({
1149
+ instanceId: telegramInstanceId,
1150
+ registrationState: telegramBusFollowerRegistrationState,
1151
+ recordRuntimeEvent,
1152
+ receiver: {
1153
+ socketPath: getTelegramBusFollowerSocketPath,
1154
+ getContext: telegramSessionContextStore.get,
1155
+ getAuthSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1156
+ getRecipientBindingKey: getTelegramManualFollowerProfileKey,
1157
+ durableAdmission: followerDurableAdmissionRuntime,
1158
+ handleQueueHandoff: acceptStagedQueueHandoff,
1159
+ },
1160
+ targetReplacement: {
1161
+ topicTargetStore: threadStore,
1162
+ getManualFollowerProfileKey: getTelegramManualFollowerProfileKey,
1163
+ manualFollowerOwnerId: telegramManualFollowerOwnerId,
1164
+ getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
1165
+ getSyncState: telegramSyncStateRuntime.getState,
1166
+ setSyncState: telegramSyncStateRuntime.setState,
1167
+ updateStatus,
1168
+ },
1169
+ recovery: {
1170
+ getLeaderState: lockRuntime.getState,
1171
+ setLifecyclePhase: telegramBusFollowerControlState.setLifecyclePhase,
1172
+ updateStatus,
1173
+ promoteToLeader: promoteTelegramBusFollowerToLeader,
1174
+ getActiveContext: telegramSessionContextStore.get,
1175
+ },
1176
+ registration: {
1177
+ protocolIdentity: telegramBusProtocolIdentity,
1178
+ getFollowerBusSocketPath: getTelegramBusFollowerSocketPath,
1179
+ getLeaderSocketPath: getTelegramBusSocketPath,
1180
+ isContextActive: telegramSessionContextStore.isCurrent,
1181
+ createRequestId: telegramBusFollowerClients.createRequestId,
1182
+ setActiveAuthSecret:
1183
+ telegramBusFollowerControlState.setActiveAuthSecret,
1184
+ getProfileKey: getTelegramManualFollowerProfileKey,
1185
+ getThreadName() {
1186
+ return telegramThreadCapabilityState.getRequestedThreadName();
1187
+ },
1188
+ getProcessBirthId() {
1189
+ return telegramQueueProcessBirthId;
1190
+ },
1191
+ getSessionGeneration: telegramSessionContextStore.getGeneration,
1192
+ async onRegistered(ctx) {
1193
+ await followerAdmissionLifecycleRuntime.onTransportChanged(ctx);
1194
+ queueHandoffReconciliationBinding.request(ctx);
1195
+ },
1196
+ onDisplayTitleChanged: updateStatus,
1197
+ },
1198
+ });
1199
+ const telegramBusFollowerRegistration =
1200
+ telegramBusFollowerAssembly.registration;
1201
+ const { admission: pollingAdmissionRuntime } =
1202
+ Polling.createTelegramDurablePollingRuntimeAssembly<
1203
+ TelegramApi.TelegramUpdate,
1204
+ Pi.ExtensionContext
1205
+ >({
1206
+ state: pollingControllerState,
1207
+ canStart(ctx) {
1208
+ return telegramSessionContextStore.isCurrent(ctx) && lockRuntime.owns(ctx);
1209
+ },
1210
+ onPersistentConflict(ctx, count): Promise<void> {
1211
+ return lockedPollingRuntime.onPersistentConflict(ctx, count);
1212
+ },
1213
+ getConfig: configStore.get,
1214
+ hasBotToken: configStore.hasBotToken,
1215
+ deleteWebhook,
1216
+ getUpdates,
1217
+ persistConfig: persistTelegramConfigWithSync,
1218
+ prepareUpdateBatch: textGroupRuntime.prepareUpdateBatch,
1219
+ journal: {
1220
+ appendBatch(updates, acceptedThroughUpdateId) {
1221
+ return updateAdmissionLifecycleRuntime.appendBatch(
1222
+ updates as Journal.TelegramJournaledUpdate[],
1223
+ acceptedThroughUpdateId,
1224
+ );
1225
+ },
1226
+ getAcceptedThroughUpdateId() {
1227
+ return Journal.withTelegramResolvedUpdateJournalReference({
1228
+ registry: telegramJournalReferenceRegistry,
1229
+ resolveBinding: resolveTelegramUpdateJournalBinding,
1230
+ referenceClass: "polling-cursor",
1231
+ operation(binding) { return binding.journal.read().acceptedThroughUpdateId; },
1232
+ });
1233
+ },
1234
+ async prepareCursorCutover() {
1235
+ const cutover = Journal.withTelegramResolvedUpdateJournalReference({
1236
+ registry: telegramJournalReferenceRegistry,
1237
+ resolveBinding: resolveTelegramUpdateJournalBinding,
1238
+ referenceClass: "polling-cursor",
1239
+ operation(binding) { return Polling.cutOverTelegramPollingCursor({
1240
+ getLegacyCursor: configStore.getLegacyPollingCursor,
1241
+ readJournal: binding.journal.read,
1242
+ publishJournalCursor(acceptedThroughUpdateId) {
1243
+ binding.journal.appendBatch([], acceptedThroughUpdateId);
1244
+ },
1245
+ async removeLegacyCursor() {
1246
+ configStore.removeLegacyPollingCursor();
1247
+ await persistTelegramConfigWithSync();
1248
+ },
1249
+ }); },
1250
+ });
1251
+ if (!cutover) throw new Error("Telegram update journal binding is unavailable.");
1252
+ await cutover;
1253
+ },
1254
+ getEntryCount: updateAdmissionLifecycleRuntime.getJournalEntryCount,
1255
+ signalWorker: updateAdmissionLifecycleRuntime.signal,
1256
+ getBootstrapEntryCount() {
1257
+ return Journal.withTelegramResolvedUpdateJournalReference({
1258
+ registry: telegramJournalReferenceRegistry,
1259
+ resolveBinding: resolveTelegramUpdateJournalBinding,
1260
+ referenceClass: "polling-bootstrap",
1261
+ operation(binding) { return binding.journal.read().entries.length; },
1262
+ }) ?? 0;
1263
+ },
1264
+ onSessionStart: updateAdmissionLifecycleRuntime.onSessionStart,
1265
+ },
1266
+ stopTypingLoop: typing.stop,
1267
+ updateStatus,
1268
+ onPollingStateChange: runtimeDiagnostics.scheduleSnapshotPersist,
1269
+ recordRuntimeEvent,
1270
+ });
1271
+ const authorizeFollowerApiCall = Bus.createTelegramFollowerApiCallAuthorizer({
1272
+ isMessageOwned: messageOwnershipRuntime.isOwnedByFollower,
1273
+ });
1274
+ const telegramBusLeaderRuntime =
1275
+ BusLeader.createTelegramBusLeaderRuntimeAssembly<Pi.ExtensionContext>({
1276
+ runtime: {
1277
+ socketPath: getTelegramBusSocketPath,
1278
+ commitEndpointPublication(commit) {
1279
+ return lockRuntime.commitIfOwned(commit);
1280
+ },
1281
+ followerRegistry: telegramBusFollowerRegistry,
1282
+ authSecret: telegramBusAuthSecret,
1283
+ protocolIdentity: telegramBusProtocolIdentity,
1284
+ startPolling: pollingAdmissionRuntime.start,
1285
+ stopPolling: pollingAdmissionRuntime.stop,
1286
+ authorizeFollowerApiCall,
1287
+ resolveAgentTarget(follower, selector) {
1288
+ return agentMessageRuntime.resolveTarget(selector, follower.target);
1289
+ },
1290
+ routeAgentMessage(follower, message) {
1291
+ return agentMessageRuntime.route({
1292
+ sourceTarget: follower.target,
1293
+ sourceThreadName: follower.threadName,
1294
+ message,
1295
+ });
1296
+ },
1297
+ isFollowerProcessAlive: Locks.isProcessAlive,
1298
+ shouldCleanupConfirmedDeadFollower:
1299
+ configControls.resolveAutomaticThreadCleanupEnabled,
1300
+ recordFollowerMessageOwnership(record) {
1301
+ messageOwnershipRuntime.recordFollower(record);
1302
+ },
1303
+ },
1304
+ getAllowedUserId: configStore.getAllowedUserId,
1305
+ instanceId: telegramInstanceId,
1306
+ getCwd: Pi.getExtensionContextCwd,
1307
+ getTelegramProfile: configStore.getActiveProfileName,
1308
+ getThreadDisplayMode() {
1309
+ return Config.resolveTelegramThreadDisplayMode(configStore.get());
1310
+ },
1311
+ persistThreadDisplayMode(mode, isCurrent) {
1312
+ return Config.setTelegramThreadDisplayMode(configStore, mode, isCurrent);
1313
+ },
1314
+ onThreadDisplayChanged() {
1315
+ const ctx = telegramSessionContextStore.get();
1316
+ if (ctx) updateStatus(ctx);
1317
+ },
1318
+ shouldForceFreshUnnamed:
1319
+ telegramThreadCapabilityState.shouldForceFreshLeaderThread,
1320
+ getRequestedThreadName:
1321
+ telegramThreadCapabilityState.getRequestedThreadName,
1322
+ topicTargetStore: threadStore,
1323
+ getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
1324
+ runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1325
+ captureWorkspaceExternalProtection,
1326
+ callApi(method, body, options) {
1327
+ return directTelegramApiRuntime.call(method, body, options);
1328
+ },
1329
+ callMultipart: directTelegramApiRuntime.callMultipart,
1330
+ downloadFile: directTelegramApiRuntime.downloadFile,
1331
+ recoverStaleTargetError: recoverStaleTelegramTopicApiError,
1332
+ getCurrentLeaderEpoch,
1333
+ getThreadReconciliationMachineState: threadReconciliationRuntime.getState,
1334
+ recordThreadReconciliationPlan,
1335
+ getSyncState: telegramSyncStateRuntime.getState,
1336
+ setSyncState: telegramSyncStateRuntime.setState,
1337
+ setLeaderTarget: telegramBusLeaderState.set,
1338
+ onProvisioningStart: telegramProvisioningActivity.start,
1339
+ onProvisioningEnd: telegramProvisioningActivity.end,
1340
+ recordRuntimeEvent,
1341
+ });
1342
+ queueHandoffReconciliationBinding.set(
1343
+ Updates.createTelegramQueueHandoffReconciliationRuntimeAssembly({
1344
+ ownsDirect: lockRuntime.owns,
1345
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
1346
+ isBusEnabled: isTelegramBusRuntimeEnabled,
1347
+ canHandoffWithLeader() {
1348
+ return Bus.hasTelegramBusCapability(
1349
+ telegramBusFollowerRegistrationState.getLeaderProtocol(),
1350
+ Bus.TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
1351
+ );
1352
+ },
1353
+ listFollowers: telegramBusFollowerRegistry.list,
1354
+ createRecipientJournalResolver:
1355
+ telegramJournalBindingRuntime.createRecipientResolver,
1356
+ queueStore: telegramQueueStore,
1357
+ admission: updateAdmissionRuntimeBinding,
1358
+ createHandoffToken: Journal.createTelegramUpdateQueueHandoffToken,
1359
+ createRequestId: telegramBusFollowerClients.createRequestId,
1360
+ donorInstanceId: telegramInstanceId,
1361
+ authSecret: telegramBusAuthSecret,
1362
+ stageThroughFollower: telegramBusFollowerClients.queueHandoff,
1363
+ routeThroughLeader: telegramBusLeaderRuntime.routeQueueHandoff,
1364
+ recordRuntimeEvent,
1365
+ }),
1366
+ );
1367
+ const telegramLeaderHealthRuntime = Sync.createTelegramLeaderHealthRuntime({
1368
+ callGetMe() {
1369
+ return directTelegramApiRuntime.call("getMe", {});
1370
+ },
1371
+ getSyncState: telegramSyncStateRuntime.getState,
1372
+ setSyncState: telegramSyncStateRuntime.setState,
1373
+ recordEvent: recordRuntimeEvent,
1374
+ });
1375
+ const telegramThreadCapabilityRuntime =
1376
+ Polling.createTelegramThreadCapabilityOrchestration<
1377
+ Pi.ExtensionContext,
1378
+ Locks.TelegramLockEntry
1379
+ >({
1380
+ state: telegramThreadCapabilityState,
1381
+ getAllowedUserId: configStore.getAllowedUserId,
1382
+ callApi: callTelegramApi,
1383
+ topicTargetStore: threadStore,
1384
+ isBusRuntimeEnabled: isTelegramBusRuntimeEnabled,
1385
+ ownsLock: lockRuntime.owns,
1386
+ isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
1387
+ startClassicPolling: pollingAdmissionRuntime.start,
1388
+ stopClassicPolling: pollingAdmissionRuntime.stop,
1389
+ startBusLeaderPolling: telegramBusLeaderRuntime.startPolling,
1390
+ stopBusLeaderPolling: telegramBusLeaderRuntime.stopPolling,
1391
+ startLeaderHealth: telegramLeaderHealthRuntime.start,
1392
+ stopLeaderHealth: telegramLeaderHealthRuntime.stop,
1393
+ registerFollowerWithLeader:
1394
+ telegramBusFollowerRegistration.registerWithLeader,
1395
+ restoreFollowerWithLeader(ctx, owner) {
1396
+ return telegramBusFollowerRegistration.registerWithLeader(ctx, owner, {
1397
+ restoreWorkspace: true,
1398
+ });
1399
+ },
1400
+ hasRememberedWorkspaceBinding(ctx) {
1401
+ return threadStore.hasWorkspaceBinding(Pi.getExtensionContextCwd(ctx));
1402
+ },
1403
+ suspendLiveThreadTarget: telegramBusLeaderState.clear,
1404
+ stopFollowerRegistration: telegramBusFollowerRegistration.stop,
1405
+ isTopicModeUnavailableError: Threads.isTelegramTopicModeUnavailableError,
1406
+ updateStatus,
1407
+ recordEvent: recordRuntimeEvent,
1408
+ });
1409
+ const telegramThreadCapabilityMonitor =
1410
+ telegramThreadCapabilityRuntime.monitor;
1411
+ observedThreadTargetBinding.set(
1412
+ telegramThreadCapabilityRuntime.observeTarget,
1413
+ );
1414
+ const threadAwarePollingPorts = telegramThreadCapabilityRuntime.pollingPorts;
1415
+ const lockedPollingRuntime = Locks.createTelegramLockedPollingRuntime({
1416
+ lock: lockRuntime,
1417
+ transportMonitor: telegramThreadCapabilityMonitor,
1418
+ hasBotToken: configStore.hasBotToken,
1419
+ getBotTokenDiagnostic: configStore.getBotTokenDiagnostic,
1420
+ canStartPolling: Pi.canStartPollingInExtensionContext,
1421
+ isContextCurrent: telegramSessionContextStore.isCurrent,
1422
+ formatStartBlockedMessage: Pi.formatPollingStartBlockedByRunMode,
1423
+ startPolling: threadAwarePollingPorts.startPolling,
1424
+ stopPolling: threadAwarePollingPorts.stopPolling,
1425
+ registerFollowerWithOwner:
1426
+ threadAwarePollingPorts.registerFollowerWithOwner,
1427
+ restoreFollowerWithOwner:
1428
+ threadAwarePollingPorts.restoreFollowerWithOwner,
1429
+ stopFollowerRegistration: threadAwarePollingPorts.stopFollowerRegistration,
1430
+ onTransportAvailabilityChanged() {
1431
+ modelContextAvailabilityRuntime.reconcile();
1432
+ const ctx = telegramSessionContextStore.get();
1433
+ if (ctx) queueHandoffReconciliationBinding.request(ctx);
1434
+ },
1435
+ updateStatus,
1436
+ recordRuntimeEvent,
1437
+ });
1438
+ const {
1439
+ disconnect: disconnectTelegramAndDeleteCurrentThread,
1440
+ cleanupForSessionRestart: cleanupTelegramThreadForSessionRestart,
1441
+ } = Sync.createTelegramThreadDisconnectAssembly({
1442
+ instanceId: telegramInstanceId,
1443
+ getCurrentThreadRecord: findCurrentThreadRecord,
1444
+ topicTargetStore: threadStore,
1445
+ callApi: callTelegramApi,
1446
+ getCurrentLeaderEpoch,
1447
+ getLeaderTarget: telegramBusLeaderState.getTarget,
1448
+ clearLeaderTarget: telegramBusLeaderState.clear,
1449
+ disconnectFollowerThread:
1450
+ telegramBusFollowerRegistration.disconnectFromLeader,
1451
+ getSyncState: telegramSyncStateRuntime.getState,
1452
+ setSyncState: telegramSyncStateRuntime.setState,
1453
+ stopPolling: lockedPollingRuntime.stop,
1454
+ suspendPolling: lockedPollingRuntime.suspend,
1455
+ recordRuntimeEvent,
1456
+ runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1457
+ });
1458
+ const telegramBridgeSessionLifecycleDeps =
1459
+ Lifecycle.createTelegramBridgeSessionLifecycleDeps({
1460
+ contextStore: telegramSessionContextStore,
1461
+ queue: {
1462
+ getCurrentModel: getContextModel,
1463
+ loadConfig: configStore.load,
1464
+ setQueuedItems: telegramQueueStore.setQueuedItems,
1465
+ setCurrentModel: currentModelRuntime.set,
1466
+ setPendingModelSwitch: pendingModelSwitchStore.set,
1467
+ syncCounters: queue.syncCounters,
1468
+ syncFlags: lifecycle.syncFlags,
1469
+ bindDeferredDispatchContext: deferredQueueDispatchRuntime.bind,
1470
+ prepareTempDir,
1471
+ updateStatus,
1472
+ unbindDeferredDispatchContext: deferredQueueDispatchRuntime.unbind,
1473
+ discardQueuedItems: queueMutationRuntime.clear,
1474
+ clearModelMenuState: modelMenuRuntime.clear,
1475
+ getActiveTurnChatId: activeTurnRuntime.getChatId,
1476
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
1477
+ clearPreview: previewRuntime.clear,
1478
+ clearActiveTurn: activeTurnRuntime.clear,
1479
+ clearAbort: abort.clearHandler,
1480
+ recordRuntimeEvent,
1481
+ },
1482
+ follower: {
1483
+ registrationState: telegramBusFollowerRegistrationState,
1484
+ registrationRuntime: telegramBusFollowerRegistration,
1485
+ instanceId: telegramInstanceId,
1486
+ suspendPolling: lockedPollingRuntime.suspend,
1487
+ isLeader: lockRuntime.owns,
1488
+ getLeaderBinding: currentInstanceThreadRuntime.getRestorationIdentity,
1489
+ getActiveContext: telegramSessionContextStore.get,
1490
+ getActiveProfileName: configStore.getActiveProfileName,
1491
+ getLeaderState: lockRuntime.getState,
1492
+ updateStatus,
1493
+ recordRuntimeEvent,
1494
+ },
1495
+ services: {
1496
+ mediaGroup: {
1497
+ resume: mediaGroupRuntime.resume,
1498
+ suspend: mediaGroupRuntime.suspend,
1499
+ },
1500
+ textGroup: {
1501
+ resume: textGroupRuntime.resume,
1502
+ suspend: textGroupRuntime.suspend,
1503
+ },
1504
+ delivery: deliveryLifecycleRuntime,
1505
+ polling: lockedPollingRuntime,
1506
+ inboundWorker: {
1507
+ onSessionShutdown: updateAdmissionRuntimeBinding.onSessionShutdown,
1508
+ },
1509
+ capabilityMonitor: telegramThreadCapabilityMonitor,
1510
+ queueWatchdog: queueDispatchWatchdogRuntime,
1511
+ },
1512
+ });
1513
+ const sessionLifecycleRuntime =
1514
+ Lifecycle.createTelegramBridgeSessionLifecycleAssembly(
1515
+ telegramBridgeSessionLifecycleDeps,
1516
+ );
1517
+
1518
+ // --- Extension API Bindings ---
1519
+
1520
+ telegramThreadDisplayNameRenameBinding.bind({
1521
+ async rename(
1522
+ expectedTarget: Parameters<Commands.TelegramThreadDisplayNameRenamePort>[0],
1523
+ threadName: string,
1524
+ ) {
1525
+ try {
1526
+ if (telegramBusFollowerRegistrationState.isRegistered()) {
1527
+ if (typeof expectedTarget.threadId !== "number") {
1528
+ return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1529
+ }
1530
+ const renamedThreadName =
1531
+ await telegramBusFollowerRegistration.renameThread?.(
1532
+ { chatId: expectedTarget.chatId, threadId: expectedTarget.threadId },
1533
+ threadName,
1534
+ );
1535
+ if (!renamedThreadName) {
1536
+ return {
1537
+ ok: false,
1538
+ message: "Telegram follower Workspace Thread rename is unavailable.",
1539
+ };
1540
+ }
1541
+ return {
1542
+ ok: true,
1543
+ threadName: renamedThreadName,
1544
+ };
1545
+ }
1546
+ if (!ownsTelegramDirectDelivery()) {
1547
+ return {
1548
+ ok: false,
1549
+ message: "Telegram Workspace Thread rename requires an active leader or follower connection.",
1550
+ };
1551
+ }
1552
+ if (typeof expectedTarget.threadId !== "number") {
1553
+ return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1554
+ }
1555
+ const renamed = await telegramBusLeaderRuntime.renameLeaderThreadAdmitted(
1556
+ threadName,
1557
+ { chatId: expectedTarget.chatId, threadId: expectedTarget.threadId },
1558
+ );
1559
+ telegramBusLeaderState.set({
1560
+ target: renamed.target,
1561
+ slot: renamed.slot,
1562
+ threadName: renamed.manualThreadName ?? renamed.threadName,
1563
+ });
1564
+ return {
1565
+ ok: true,
1566
+ threadName: renamed.manualThreadName,
1567
+ };
1568
+ } catch (error) {
1569
+ return {
1570
+ ok: false,
1571
+ message:
1572
+ error instanceof Error
1573
+ ? error.message
1574
+ : "Telegram Workspace Thread rename failed.",
1575
+ };
1576
+ }
1577
+ },
1578
+ }.rename);
1579
+ telegramThreadDisplayNameResetBinding.bind({
1580
+ async reset(
1581
+ expectedTarget: Parameters<Commands.TelegramThreadDisplayNameResetPort>[0],
1582
+ ) {
1583
+ try {
1584
+ if (typeof expectedTarget.threadId !== "number") {
1585
+ return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1586
+ }
1587
+ const target = {
1588
+ chatId: expectedTarget.chatId,
1589
+ threadId: expectedTarget.threadId,
1590
+ };
1591
+ const wasFollower = telegramBusFollowerRegistrationState.isRegistered();
1592
+ const result = wasFollower
1593
+ ? await telegramBusFollowerRegistration.resetThreadName?.(target)
1594
+ : (await telegramBusLeaderRuntime.resetLeaderThreadName(target)).threadName;
1595
+ if (!result) {
1596
+ return { ok: false, message: "Thread display name reset is unavailable." };
1597
+ }
1598
+ const leaderTarget = telegramBusLeaderState.getTarget();
1599
+ if (!wasFollower && leaderTarget) {
1600
+ telegramBusLeaderState.set({ target: leaderTarget, threadName: result });
1601
+ }
1602
+ return {
1603
+ ok: true,
1604
+ threadName: result,
1605
+ };
1606
+ } catch (error) {
1607
+ return {
1608
+ ok: false,
1609
+ message: error instanceof Error
1610
+ ? error.message : "Thread display name reset failed.",
1611
+ };
1612
+ }
1613
+ },
1614
+ }.reset);
1615
+ Bindings.registerTelegramCommandsAndTools({
1616
+ pi,
1617
+ agentDir: Paths.resolveAgentDir(),
1618
+ configStore,
1619
+ persistConfig: persistTelegramConfigWithSync,
1620
+ setup,
1621
+ activeTurnRuntime,
1622
+ lockedPollingRuntime,
1623
+ stopPolling: disconnectTelegramAndDeleteCurrentThread,
1624
+ recoverPollingStart: Recovery.createTelegramPollingStartRecoveryHandler({
1625
+ getOwnersPath: Paths.resolveTelegramOwnersPath,
1626
+ getStatePaths() {
1627
+ return [
1628
+ Threads.getTelegramTopicTargetsPath(
1629
+ undefined,
1630
+ configStore.getActiveProfileName(),
1631
+ ),
1632
+ ];
1633
+ },
1634
+ suspendPolling: lockedPollingRuntime.suspend,
1635
+ releaseOwnership: lockRuntime.release,
1636
+ recordRuntimeEvent,
1637
+ }),
1638
+ getDisconnectThreadName() {
1639
+ const record = findCurrentThreadRecord();
1640
+ if (!record?.target.threadId) return undefined;
1641
+ return record.threadName ?? "current Telegram thread";
1642
+ },
1643
+ setRequestedThreadNameForPollingStart:
1644
+ telegramThreadCapabilityState.setRequestedThreadName,
1645
+ validateThreadName(threadName) {
1646
+ return Threads.getTelegramTopicThreadNameValidationError(
1647
+ threadName,
1648
+ undefined,
1649
+ );
1650
+ },
1651
+ onTransportChanged() {
1652
+ deliveryLifecycleRuntime.onSessionStart();
1653
+ activityVerbosityRuntime.reset();
1654
+ modelContextAvailabilityRuntime.reconcile();
1655
+ },
1656
+ getStatusLines,
1657
+ buttonActionStore,
1658
+ sendMarkdownReply,
1659
+ async sendChannelMarkdownMessage(channel, markdown, options) {
1660
+ if (!lockRuntime.owns()) {
1661
+ throw new Error("Telegram channel delivery requires direct leader transport ownership.");
1662
+ }
1663
+ const profileName = configStore.getActiveProfileName() ?? "default";
1664
+ const botToken = configStore.getBotToken();
1665
+ if (!botToken) throw new Error("Telegram channel delivery requires an active bot token.");
1666
+ const store = ChannelPosts.createTelegramChannelPostJournalStore({
1667
+ path: Paths.resolveTelegramChannelPostJournalPath(undefined, profileName),
1668
+ profileName,
1669
+ tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
1670
+ });
1671
+ const record = await ChannelPosts.publishTelegramChannelPost({
1672
+ store, operationId: options.operationId,
1673
+ channel: channel as ChannelPosts.TelegramChannelPostAddress, markdown,
1674
+ async observeChannel(channelAddress) {
1675
+ return telegramApiRuntime.call("getChat", { chat_id: channelAddress });
1676
+ },
1677
+ async send(channelAddress, body) {
1678
+ const sent = await telegramApiRuntime.call<TelegramApi.TelegramSentMessage & {
1679
+ chat: { id: number; type: string };
1680
+ }>("sendRichMessage", { chat_id: channelAddress, rich_message: { markdown: body },
1681
+ ...(options.replyMarkup ? { reply_markup: options.replyMarkup } : {}) });
1682
+ return { messageId: sent.message_id, chat: sent.chat };
1683
+ },
1684
+ });
1685
+ return record.state === "published" ? record.messageId : undefined;
1686
+ },
1687
+ async sendChannelMediaMessage(channel, mediaPath, markdown, options) {
1688
+ if (!lockRuntime.owns()) {
1689
+ throw new Error("Telegram channel media delivery requires direct leader transport ownership.");
1690
+ }
1691
+ const profileName = configStore.getActiveProfileName() ?? "default";
1692
+ const botToken = configStore.getBotToken();
1693
+ if (!botToken) throw new Error("Telegram channel media delivery requires an active bot token.");
1694
+ const media = await ChannelPosts.inspectTelegramChannelPostMedia(mediaPath);
1695
+ const caption = Replies.renderTelegramMarkdownToHtmlDraft(markdown);
1696
+ ChannelPosts.assertTelegramChannelPostCaptionWithinLimit(caption);
1697
+ const store = ChannelPosts.createTelegramChannelPostJournalStore({
1698
+ path: Paths.resolveTelegramChannelPostJournalPath(undefined, profileName),
1699
+ profileName,
1700
+ tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
1701
+ });
1702
+ const record = await ChannelPosts.publishTelegramChannelPost({
1703
+ store, operationId: options.operationId,
1704
+ channel: channel as ChannelPosts.TelegramChannelPostAddress, markdown, media,
1705
+ async observeChannel(channelAddress) {
1706
+ return telegramApiRuntime.call("getChat", { chat_id: channelAddress });
1707
+ },
1708
+ async send(channelAddress) {
1709
+ const sent = await telegramApiRuntime.callMultipart<TelegramApi.TelegramSentMessage & {
1710
+ chat: { id: number; type: string };
1711
+ }>(media.kind === "photo" ? "sendPhoto" : "sendVideo", {
1712
+ chat_id: String(channelAddress),
1713
+ caption,
1714
+ parse_mode: "HTML",
1715
+ ...(options.replyMarkup ? { reply_markup: JSON.stringify(options.replyMarkup) } : {}),
1716
+ }, media.kind === "photo" ? "photo" : "video", mediaPath, media.fileName);
1717
+ return { messageId: sent.message_id, chat: sent.chat };
1718
+ },
1719
+ });
1720
+ return record.state === "published" ? record.messageId : undefined;
1721
+ },
1722
+ listChannelPosts(input) {
1723
+ const profileName = configStore.getActiveProfileName() ?? "default";
1724
+ const botToken = configStore.getBotToken();
1725
+ if (!botToken) throw new Error("Telegram channel posts require an active bot token.");
1726
+ return ChannelPosts.createTelegramChannelPostJournalStore({
1727
+ path: Paths.resolveTelegramChannelPostJournalPath(undefined, profileName),
1728
+ profileName,
1729
+ tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
1730
+ }).list(input);
1731
+ },
1732
+ async mutateChannelPost(input) {
1733
+ if (!lockRuntime.owns()) throw new Error("Telegram channel post mutation requires direct leader ownership.");
1734
+ const profileName = configStore.getActiveProfileName() ?? "default";
1735
+ const botToken = configStore.getBotToken();
1736
+ if (!botToken) throw new Error("Telegram channel post mutation requires an active bot token.");
1737
+ const store = ChannelPosts.createTelegramChannelPostJournalStore({
1738
+ path: Paths.resolveTelegramChannelPostJournalPath(undefined, profileName), profileName,
1739
+ tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
1740
+ });
1741
+ if (input.action === "edit") {
1742
+ if (!input.markdown) throw new Error("Telegram channel post edit requires markdown.");
1743
+ const current = store.get(input.operationId);
1744
+ const caption = current?.media
1745
+ ? Replies.renderTelegramMarkdownToHtmlDraft(input.markdown) : undefined;
1746
+ if (caption !== undefined) {
1747
+ ChannelPosts.assertTelegramChannelPostCaptionWithinLimit(caption);
1748
+ }
1749
+ const begun = store.beginEdit({ operationId: input.operationId,
1750
+ mutationId: input.mutationId, markdown: input.markdown });
1751
+ if (!begun.began) {
1752
+ if (begun.record.state === "published" && begun.record.lastMutationId === input.mutationId)
1753
+ return begun.record;
1754
+ throw new Error("Telegram channel post edit outcome is unknown; refusing automatic replay.");
1755
+ }
1756
+ if (begun.record.state !== "edit-outcome-unknown") throw new Error("Telegram channel post edit authority is invalid.");
1757
+ if (begun.record.media) {
1758
+ if (caption === undefined) {
1759
+ throw new Error("Telegram channel post media caption edit requires retained media identity.");
1760
+ }
1761
+ await telegramApiRuntime.call("editMessageCaption", { chat_id: begun.record.channelId,
1762
+ message_id: begun.record.messageId, caption, parse_mode: "HTML" });
1763
+ } else {
1764
+ await telegramApiRuntime.call("editMessageText", { chat_id: begun.record.channelId,
1765
+ message_id: begun.record.messageId,
1766
+ text: Replies.renderTelegramMarkdownToHtmlDraft(input.markdown), parse_mode: "HTML" });
1767
+ }
1768
+ return store.confirmEdited({ operationId: input.operationId, mutationId: input.mutationId }).record;
1769
+ }
1770
+ if (input.markdown !== undefined) throw new Error("Telegram channel post deletion does not accept markdown.");
1771
+ const begun = store.beginDelete({ operationId: input.operationId, mutationId: input.mutationId });
1772
+ if (!begun.began) {
1773
+ if (begun.record.state === "deleted" && begun.record.mutationId === input.mutationId) return begun.record;
1774
+ throw new Error("Telegram channel post deletion outcome is unknown; refusing automatic replay.");
1775
+ }
1776
+ if (begun.record.state !== "delete-outcome-unknown") throw new Error("Telegram channel post deletion authority is invalid.");
1777
+ await telegramApiRuntime.call("deleteMessage", { chat_id: begun.record.channelId,
1778
+ message_id: begun.record.messageId });
1779
+ return store.confirmDeleted({ operationId: input.operationId, mutationId: input.mutationId }).record;
1780
+ },
1781
+ callMultipart,
1782
+ getDefaultChatId: proactivePushChatIdGetter,
1783
+ getDefaultTarget: proactivePushTargetGetter,
1784
+ ...agentMessageToolRoutingRuntime,
1785
+ updateStatus,
1786
+ recordRuntimeEvent,
1787
+ });
1788
+
1789
+ // --- Lifecycle Hooks ---
1790
+
1791
+ Bindings.registerTelegramLifecycleRuntimeHooks({
1792
+ pi,
1793
+ sessionLifecycleRuntime: {
1794
+ ...sessionLifecycleRuntime,
1795
+ onModelSelect: currentModelRuntime.onModelSelect,
1796
+ },
1797
+ activityRuntime,
1798
+ activityVerbosityRuntime,
1799
+ assistantOutputRuntime,
1800
+ publicationRuntime,
1801
+ configStore,
1802
+ abort,
1803
+ typing,
1804
+ lifecycle,
1805
+ activeTurnRuntime,
1806
+ telegramQueueStore,
1807
+ modelSwitchController,
1808
+ previewRuntime,
1809
+ promptDispatchRuntime,
1810
+ deferredQueueDispatchRuntime,
1811
+ modelContextAvailabilityRuntime,
1812
+ disconnectOnQuit: cleanupTelegramThreadForSessionRestart,
1813
+ resolveAutomaticThreadCleanupEnabled:
1814
+ configControls.resolveAutomaticThreadCleanupEnabled,
1815
+ buttonActionStore,
1816
+ callMultipart,
1817
+ sendChatAction,
1818
+ sendRecordVoiceAction,
1819
+ sendMarkdownReply,
1820
+ sendTextReply,
1821
+ dispatchNextQueuedTelegramTurn,
1822
+ onPromptHandedOff(turn, ctx) {
1823
+ updateAdmissionRuntimeBinding
1824
+ .getSettlement()
1825
+ ?.onPromptHandedOff(turn, ctx);
1826
+ },
1827
+ answerGuestQuery,
1828
+ deleteMessage: deleteTelegramMessage,
1829
+ sendGuestReply,
1830
+ editGuestReply,
1831
+ finalizeMarkdownPreview,
1832
+ preparePreviewDelivery,
1833
+ proactivePushTargetGetter,
1834
+ getAssistantRenderingMode: configControls.getAssistantRenderingMode,
1835
+ recordMessageOwnership: messageOwnershipRuntime.recordLocal,
1836
+ canSendAgentActivity(ctx) {
1837
+ return (
1838
+ lockOwnershipGuard.ownsContext(ctx) ||
1839
+ telegramBusFollowerRegistrationState.isRegistered()
1840
+ );
1841
+ },
1842
+ isSessionContextActive(ctx) {
1843
+ return telegramSessionContextStore.isCurrent(ctx);
1844
+ },
1845
+ isTurnTransportActive(turn) {
1846
+ return telegramTransportStampRuntime.isActive(turn.transportStamp);
1847
+ },
1848
+ updateStatus,
1849
+ recordRuntimeEvent,
1850
+ });
1851
+ }