@llblab/pi-telegram 0.52.2 → 0.53.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.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +7 -11
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +639 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +29 -3
  44. package/dist/lib/locks.js +289 -108
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +61 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +21 -37
  76. package/dist/lib/polling.js +59 -50
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +249 -126
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +1 -3
  95. package/dist/lib/sync.js +67 -43
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +148 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +31 -29
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1206 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +677 -203
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +125 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +144 -147
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +511 -300
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. package/scripts/measure-workspace.mjs +0 -108
package/lib/extension.ts CHANGED
@@ -12,7 +12,6 @@ import * as BusLeader from "./bus-leader.ts";
12
12
  import * as BusTransport from "./bus-transport.ts";
13
13
  import * as Bus from "./bus.ts";
14
14
  import * as ChannelPosts from "./channel-posts.ts";
15
- import * as ThreadCleanupManager from "./thread-cleanup-manager.ts";
16
15
  import * as CommandTemplates from "./command-templates.ts";
17
16
  import * as Commands from "./commands.ts";
18
17
  import * as Config from "./config.ts";
@@ -33,6 +32,7 @@ import * as Paths from "./paths.ts";
33
32
  import * as Pi from "./pi.ts";
34
33
  import * as Polling from "./polling.ts";
35
34
  import * as Preview from "./preview.ts";
35
+ import * as ProcessIdentity from "./process-identity.ts";
36
36
  import * as PromptTemplates from "./prompt-templates.ts";
37
37
  import * as Prompts from "./prompts.ts";
38
38
  import * as Queue from "./queue.ts";
@@ -44,12 +44,13 @@ import * as Sections from "./sections.ts";
44
44
  import * as Skills from "./skills.ts";
45
45
  import * as Status from "./status.ts";
46
46
  import * as Sync from "./sync.ts";
47
+ import * as Targets from "./target.ts";
47
48
  import * as TelegramApi from "./telegram-api.ts";
48
49
  import * as TextGroups from "./text-groups.ts";
49
- import * as ThreadReconciler from "./thread-reconciler.ts";
50
- import * as Targets from "./target.ts";
50
+ import * as ThreadCleanupManager from "./thread-cleanup-manager.ts";
51
51
  import * as ThreadDisplay from "./thread-display.ts";
52
52
  import * as ThreadNaming from "./thread-naming.ts";
53
+ import * as ThreadReconciler from "./thread-reconciler.ts";
53
54
  import * as Threads from "./threads.ts";
54
55
  import * as TimeInjection from "./time-injection.ts";
55
56
  import * as Updates from "./updates.ts";
@@ -69,6 +70,11 @@ const telegramBusProtocolIdentity =
69
70
  Bus.TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT,
70
71
  Bus.TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT,
71
72
  Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE,
73
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
74
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
75
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
76
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
77
+ Bus.TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
72
78
  ]);
73
79
 
74
80
  // --- Extension Runtime ---
@@ -88,7 +94,9 @@ export default function (pi: Pi.ExtensionAPI) {
88
94
  } = piRuntime;
89
95
  const bridgeRuntime = Runtime.createTelegramBridgeRuntime();
90
96
  const runtimeDiagnostics =
91
- Logging.createTelegramRuntimeDiagnosticsRuntime<Pi.ExtensionContext>({ sharedFile: true });
97
+ Logging.createTelegramRuntimeDiagnosticsRuntime<Pi.ExtensionContext>({
98
+ sharedFile: true,
99
+ });
92
100
  const runtimeEvents = runtimeDiagnostics.events;
93
101
  const recordRuntimeEvent = runtimeDiagnostics.recordRuntimeEvent;
94
102
  const configStore = Config.createTelegramConfigStore({ recordRuntimeEvent });
@@ -161,11 +169,18 @@ export default function (pi: Pi.ExtensionAPI) {
161
169
  getBotToken: configStore.getBotToken,
162
170
  getBotId: getTelegramBotId,
163
171
  });
164
- const telegramLeaderJournalPath = Locks.createTelegramLeaderJournalPathResolver({
165
- getNamedJournalPath() { return lockRuntime.getJournalPath(); },
166
- getSessionId() { return Pi.getExtensionContextSessionId(telegramSessionContextStore.get()); },
167
- getProfileName: configStore.getActiveProfileName,
168
- });
172
+ const telegramLeaderJournalPath =
173
+ Locks.createTelegramLeaderJournalPathResolver({
174
+ getNamedJournalPath() {
175
+ return lockRuntime.getJournalPath();
176
+ },
177
+ getSessionId() {
178
+ return Pi.getExtensionContextSessionId(
179
+ telegramSessionContextStore.get(),
180
+ );
181
+ },
182
+ getProfileName: configStore.getActiveProfileName,
183
+ });
169
184
  const telegramJournalBindingRuntime =
170
185
  Journal.createTelegramUpdateJournalBindingRuntime({
171
186
  base: {
@@ -201,9 +216,13 @@ export default function (pi: Pi.ExtensionAPI) {
201
216
  getLeaderJournalPath: telegramLeaderJournalPath.resolve,
202
217
  getRuntimeDir: Paths.resolveTelegramTempDir,
203
218
  getFollowerJournalPath(bindingKey, profileName, sessionId) {
204
- if (sessionId !== undefined) return Paths.resolveTelegramSessionJournalPath(
205
- sessionId, bindingKey, undefined, profileName,
206
- );
219
+ if (sessionId !== undefined)
220
+ return Paths.resolveTelegramSessionJournalPath(
221
+ sessionId,
222
+ bindingKey,
223
+ undefined,
224
+ profileName,
225
+ );
207
226
  return Paths.resolveTelegramFollowerJournalPath(
208
227
  bindingKey,
209
228
  undefined,
@@ -228,7 +247,6 @@ export default function (pi: Pi.ExtensionAPI) {
228
247
  telegramJournalBindingRuntime.getActiveRecoveryKey;
229
248
  const isTelegramBusRuntimeEnabled =
230
249
  telegramThreadCapabilityState.isBusRuntimeEnabled;
231
- Config.bindGlobalTelegramConfigRuntime(configStore);
232
250
  const configControls = Config.createTelegramConfigControls(configStore);
233
251
  const lockRuntime = Locks.createTelegramLockRuntime<Pi.ExtensionContext>({
234
252
  key: Locks.createTelegramLockKeyResolver(configStore),
@@ -237,7 +255,9 @@ export default function (pi: Pi.ExtensionAPI) {
237
255
  busSecret: telegramBusAuthSecret,
238
256
  staleHeartbeatMs: Locks.TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS,
239
257
  legacyLocksPath: Paths.resolveLegacyTelegramOwnersPath(),
240
- createJournalPath() { return telegramLeaderJournalPath.createJournalPath(); },
258
+ createJournalPath() {
259
+ return telegramLeaderJournalPath.createJournalPath();
260
+ },
241
261
  });
242
262
  const telegramSessionContextStore =
243
263
  Lifecycle.createTelegramSessionContextStore<Pi.ExtensionContext>({
@@ -245,8 +265,11 @@ export default function (pi: Pi.ExtensionAPI) {
245
265
  return ctx.sessionManager ?? ctx.cwd;
246
266
  },
247
267
  });
248
- const captureTelegramStateAuthority = Locks.createTelegramOwnedStateAuthorityCapture(
249
- lockRuntime, telegramSessionContextStore);
268
+ const captureTelegramStateAuthority =
269
+ Locks.createTelegramOwnedStateAuthorityCapture(
270
+ lockRuntime,
271
+ telegramSessionContextStore,
272
+ );
250
273
  const threadStore = Threads.createTelegramTopicTargetStore({
251
274
  path: Paths.resolveTelegramStatePath,
252
275
  telegramProfile: function () {
@@ -261,11 +284,12 @@ export default function (pi: Pi.ExtensionAPI) {
261
284
  return workspaceAdmissionRuntime.resolve()?.listReservedSlots() ?? [];
262
285
  },
263
286
  });
264
- const resolveWorkspaceRestoreStore = Threads.createTelegramWorkspaceRestoreResolver({
265
- getProfileName: configStore.getActiveProfileName,
266
- getBotToken: configStore.getBotToken,
267
- threadStore,
268
- });
287
+ const resolveWorkspaceRestoreStore =
288
+ Threads.createTelegramWorkspaceRestoreResolver({
289
+ getProfileName: configStore.getActiveProfileName,
290
+ getBotToken: configStore.getBotToken,
291
+ threadStore,
292
+ });
269
293
  runtimeDiagnostics.bindStorage({
270
294
  getBotToken: configStore.getBotToken,
271
295
  getProfileName: configStore.getActiveProfileName,
@@ -283,12 +307,15 @@ export default function (pi: Pi.ExtensionAPI) {
283
307
  instanceId: telegramInstanceId,
284
308
  isRegisteredFor(target) {
285
309
  const registered = telegramBusFollowerRegistrationState.getTarget();
286
- return telegramBusFollowerRegistrationState.isRegistered() &&
310
+ return (
311
+ telegramBusFollowerRegistrationState.isRegistered() &&
287
312
  registered?.chatId === target.chatId &&
288
- registered.threadId === target.threadId;
313
+ registered.threadId === target.threadId
314
+ );
289
315
  },
290
316
  async requestSessionReplacement(operation, intent) {
291
- const request = telegramBusFollowerRegistration.requestSessionReplacement;
317
+ const request =
318
+ telegramBusFollowerRegistration.requestSessionReplacement;
292
319
  if (!request) return false;
293
320
  return await request(operation, intent);
294
321
  },
@@ -298,10 +325,15 @@ export default function (pi: Pi.ExtensionAPI) {
298
325
  { text: html, parseMode: "html", replyMarkup: { inline_keyboard: [] } },
299
326
  { scope: { kind: "target", target } },
300
327
  );
301
- return delivery.ok ? { ok: true } : { ok: false,
302
- retryable: delivery.reason === "runtime-unavailable" ||
303
- delivery.reason === "target-unavailable" ||
304
- delivery.reason === "transport-retryable" };
328
+ return delivery.ok
329
+ ? { ok: true }
330
+ : {
331
+ ok: false,
332
+ retryable:
333
+ delivery.reason === "runtime-unavailable" ||
334
+ delivery.reason === "target-unavailable" ||
335
+ delivery.reason === "transport-retryable",
336
+ };
305
337
  },
306
338
  handoffTtlMs: Threads.TELEGRAM_LEADER_SESSION_HANDOFF_TTL_MS,
307
339
  recordRuntimeEvent,
@@ -346,11 +378,10 @@ export default function (pi: Pi.ExtensionAPI) {
346
378
  const proactivePushChatIdGetter =
347
379
  Config.createTelegramProactivePushChatIdGetter(proactivePushTargetGetter);
348
380
  const buttonActionStore = Outbound.createTelegramButtonActionStore();
349
- const planGenerativeAppOutput =
350
- Outbound.createTelegramOutboundReplyPlanner(
351
- buttonActionStore,
352
- configControls.getAssistantRenderingMode,
353
- );
381
+ const planGenerativeAppOutput = Outbound.createTelegramOutboundReplyPlanner(
382
+ buttonActionStore,
383
+ configControls.getAssistantRenderingMode,
384
+ );
354
385
  const pendingModelSwitchStore =
355
386
  Model.createPendingModelSwitchStore<
356
387
  Model.ScopedTelegramModel<ActivePiModel>
@@ -388,100 +419,127 @@ export default function (pi: Pi.ExtensionAPI) {
388
419
  );
389
420
  const telegramApiTargetActivityRuntime =
390
421
  TelegramApi.createTelegramApiTargetActivityRuntime();
422
+ // Retirement protection and dead-owner reclamation must inspect the same journal sources.
423
+ const workspaceRetirementJournalPorts = {
424
+ getActiveTurnTarget: activeTurnRuntime.getTarget,
425
+ getQueuedItems: telegramQueueStore.getQueuedItems,
426
+ resolveLeaderJournal: resolveTelegramUpdateJournalBinding,
427
+ createFollowerJournalResolver:
428
+ telegramJournalBindingRuntime.createLegacyRecipientResolver,
429
+ createSessionJournalResolver:
430
+ telegramJournalBindingRuntime.createRecipientResolver,
431
+ createJournalPathResolver: telegramJournalBindingRuntime.createPathResolver,
432
+ discoverFollowerJournals() {
433
+ return Journal.discoverTelegramRecipientJournalPaths({
434
+ directory: Paths.resolveTelegramTempDir(),
435
+ profileName: configStore.getActiveProfileName(),
436
+ });
437
+ },
438
+ };
391
439
  const workspaceProtectionObserver =
392
440
  WorkspaceRetirement.createTelegramWorkspaceProtectionObserver({
393
441
  listFollowers: telegramBusFollowerRegistry.list,
394
- getActiveTurnTarget: activeTurnRuntime.getTarget,
395
- getQueuedItems: telegramQueueStore.getQueuedItems,
396
- resolveLeaderJournal: resolveTelegramUpdateJournalBinding,
397
- createFollowerJournalResolver:
398
- telegramJournalBindingRuntime.createLegacyRecipientResolver,
399
- createSessionJournalResolver:
400
- telegramJournalBindingRuntime.createRecipientResolver,
442
+ ...workspaceRetirementJournalPorts,
401
443
  withJournalReference(binding, operation) {
402
- if (!binding.recoveryKey) throw new Error(
403
- "Telegram workspace journal reference identity is unavailable.",
444
+ if (!binding.recoveryKey)
445
+ throw new Error(
446
+ "Telegram workspace journal reference identity is unavailable.",
447
+ );
448
+ return telegramJournalReferenceRegistry.withReference(
449
+ {
450
+ referenceClass: "workspace-retirement",
451
+ recoveryKey: binding.recoveryKey,
452
+ },
453
+ operation,
404
454
  );
405
- return telegramJournalReferenceRegistry.withReference({
406
- referenceClass: "workspace-retirement",
407
- recoveryKey: binding.recoveryKey,
408
- }, operation);
409
- },
410
- discoverFollowerJournals() {
411
- return Journal.discoverTelegramRecipientJournalPaths({
412
- directory: Paths.resolveTelegramTempDir(),
413
- profileName: configStore.getActiveProfileName(),
414
- });
415
455
  },
416
- createJournalPathResolver: telegramJournalBindingRuntime.createPathResolver,
417
456
  inspectJournalNamespace() {
418
457
  const botToken = configStore.getBotToken();
419
- if (!botToken) throw new Error("Telegram namespace inspection requires current bot identity.");
458
+ if (!botToken)
459
+ throw new Error(
460
+ "Telegram namespace inspection requires current bot identity.",
461
+ );
420
462
  return Journal.inspectTelegramSessionJournalNamespace({
421
463
  directory: Paths.resolveTelegramTempDir(),
422
464
  profile: configStore.getActiveProfileName() ?? "default",
423
- pollingPath: telegramLeaderJournalPath.resolve(configStore.getActiveProfileName()),
424
- botIdentity: Journal.createTelegramUpdateJournalBotIdentity({ botToken, botId: getTelegramBotId() }),
425
- limits: { maxDirectoryEntries: 10_000, maxFiles: 4096, maxBytes: 64 * 1024 * 1024,
426
- maxEntries: 10_000, maxWork: 100_000 },
465
+ pollingPath: telegramLeaderJournalPath.resolve(
466
+ configStore.getActiveProfileName(),
467
+ ),
468
+ botIdentity: Journal.createTelegramUpdateJournalBotIdentity({
469
+ botToken,
470
+ botId: getTelegramBotId(),
471
+ }),
472
+ limits: {
473
+ maxDirectoryEntries: 10_000,
474
+ maxFiles: 4096,
475
+ maxBytes: 64 * 1024 * 1024,
476
+ maxEntries: 10_000,
477
+ maxWork: 100_000,
478
+ },
427
479
  });
428
480
  },
429
481
  getJournalWriterProtection(journalBindingKey) {
430
- const owner = Threads.getTelegramThreadOwnerFromProfileKey(
431
- journalBindingKey,
432
- );
482
+ const owner =
483
+ Threads.getTelegramThreadOwnerFromProfileKey(journalBindingKey);
433
484
  if (owner.kind !== "manual-follower") return "unknown";
434
- const liveness = Bus.getTelegramProcessBirthIdentityLiveness(
435
- owner.instanceId,
436
- );
485
+ const liveness =
486
+ ProcessIdentity.getTelegramProcessBirthIdentityLiveness(
487
+ owner.instanceId,
488
+ );
437
489
  return liveness === "alive"
438
490
  ? "protected"
439
- : liveness === "dead" ? "clear" : "unknown";
491
+ : liveness === "dead"
492
+ ? "clear"
493
+ : "unknown";
440
494
  },
441
495
  getDeliveryAuthorityProtection(binding) {
442
- const retainedRestore = resolveWorkspaceRestoreStore()?.list().some(function (intent) {
443
- return intent.request.binding.bindingKey === binding.bindingKey ||
444
- Targets.areTelegramTargetsEqual(intent.request.binding.target, binding.target) ||
445
- Targets.areTelegramTargetsEqual(intent.request.target, binding.target);
446
- });
447
- return retainedRestore || telegramApiTargetActivityRuntime.hasPendingTarget(binding.target)
496
+ const retainedRestore = resolveWorkspaceRestoreStore()
497
+ ?.list()
498
+ .some(function (intent) {
499
+ return (
500
+ intent.request.binding.bindingKey === binding.bindingKey ||
501
+ Targets.areTelegramTargetsEqual(
502
+ intent.request.binding.target,
503
+ binding.target,
504
+ ) ||
505
+ Targets.areTelegramTargetsEqual(
506
+ intent.request.target,
507
+ binding.target,
508
+ )
509
+ );
510
+ });
511
+ return retainedRestore ||
512
+ telegramApiTargetActivityRuntime.hasPendingTarget(binding.target)
448
513
  ? "protected"
449
514
  : "clear";
450
515
  },
451
516
  });
452
- const captureWorkspaceExternalProtection = workspaceProtectionObserver.capture;
453
- const pruneWorkspaceJournalEvidence = WorkspaceRetirement.createTelegramWorkspaceJournalEvidencePruner({
454
- store: threadStore,
455
- getAdmission: workspaceAdmissionRuntime.resolve,
456
- getLeaderEpoch: getCurrentLeaderEpoch,
457
- runExclusive: telegramWorkspaceOperationRuntime.runExclusive,
458
- protection: workspaceProtectionObserver,
459
- });
517
+ const captureWorkspaceExternalProtection =
518
+ workspaceProtectionObserver.capture;
519
+ const pruneWorkspaceJournalEvidence =
520
+ WorkspaceRetirement.createTelegramWorkspaceJournalEvidencePruner({
521
+ store: threadStore,
522
+ getAdmission: workspaceAdmissionRuntime.resolve,
523
+ getLeaderEpoch: getCurrentLeaderEpoch,
524
+ runExclusive: telegramWorkspaceOperationRuntime.runExclusive,
525
+ protection: workspaceProtectionObserver,
526
+ });
460
527
  const reclaimTelegramWorkspaceDeadOwnerQueue =
461
528
  WorkspaceRetirement.createTelegramWorkspaceDeadOwnerQueueReclaimer({
462
529
  getExternalProtection: captureWorkspaceExternalProtection,
463
- getActiveTurnTarget: activeTurnRuntime.getTarget,
464
- getQueuedItems: telegramQueueStore.getQueuedItems,
465
- resolveLeaderJournal: resolveTelegramUpdateJournalBinding,
466
- createFollowerJournalResolver:
467
- telegramJournalBindingRuntime.createLegacyRecipientResolver,
468
- createSessionJournalResolver:
469
- telegramJournalBindingRuntime.createRecipientResolver,
470
- discoverFollowerJournals() {
471
- return Journal.discoverTelegramRecipientJournalPaths({
472
- directory: Paths.resolveTelegramTempDir(),
473
- profileName: configStore.getActiveProfileName(),
474
- });
475
- },
476
- createJournalPathResolver: telegramJournalBindingRuntime.createPathResolver,
530
+ ...workspaceRetirementJournalPorts,
477
531
  withJournalReference(binding, operation) {
478
- if (!binding.recoveryKey) throw new Error(
479
- "Telegram workspace journal reference identity is unavailable.",
532
+ if (!binding.recoveryKey)
533
+ throw new Error(
534
+ "Telegram workspace journal reference identity is unavailable.",
535
+ );
536
+ return telegramJournalReferenceRegistry.withReference(
537
+ {
538
+ referenceClass: "workspace-retirement",
539
+ recoveryKey: binding.recoveryKey,
540
+ },
541
+ operation,
480
542
  );
481
- return telegramJournalReferenceRegistry.withReference({
482
- referenceClass: "workspace-retirement",
483
- recoveryKey: binding.recoveryKey,
484
- }, operation);
485
543
  },
486
544
  getRecoveryOwner() {
487
545
  return {
@@ -491,11 +549,14 @@ export default function (pi: Pi.ExtensionAPI) {
491
549
  sessionGeneration: telegramSessionContextStore.getGeneration(),
492
550
  };
493
551
  },
494
- getQueueOwnerLiveness: Bus.getTelegramProcessLiveness,
552
+ getQueueOwnerLiveness: ProcessIdentity.getTelegramProcessLiveness,
495
553
  onMutationError(error) {
496
- recordRuntimeEvent("Telegram dead-owner queue reclamation was refused.", {
497
- error: error instanceof Error ? error.message : String(error),
498
- });
554
+ recordRuntimeEvent(
555
+ "Telegram dead-owner queue reclamation was refused.",
556
+ {
557
+ error: error instanceof Error ? error.message : String(error),
558
+ },
559
+ );
499
560
  },
500
561
  isBindingCurrent(binding) {
501
562
  return WorkspaceRetirement.isCurrentTelegramWorkspaceBinding(
@@ -506,7 +567,9 @@ export default function (pi: Pi.ExtensionAPI) {
506
567
  });
507
568
  const inactiveThreadCleanupReviewRuntime =
508
569
  ThreadCleanupManager.createTelegramInactiveThreadCleanupReviewRuntime({
509
- getProfileName() { return configStore.getActiveProfileName() ?? "default"; },
570
+ getProfileName() {
571
+ return configStore.getActiveProfileName() ?? "default";
572
+ },
510
573
  listBindings: threadStore.listWorkspaceBindings,
511
574
  getProtection: captureWorkspaceExternalProtection,
512
575
  listReservations: threadStore.listReservations,
@@ -515,11 +578,20 @@ export default function (pi: Pi.ExtensionAPI) {
515
578
  getWorkStore() {
516
579
  const profileName = configStore.getActiveProfileName() ?? "default";
517
580
  const botToken = configStore.getBotToken();
518
- if (!botToken) throw new Error("Telegram Thread cleanup review requires an active bot token.");
581
+ if (!botToken)
582
+ throw new Error(
583
+ "Telegram Thread cleanup review requires an active bot token.",
584
+ );
519
585
  return ThreadCleanupManager.createTelegramThreadCleanupWorkStore({
520
- ...Paths.resolveTelegramServiceJournalStorage("thread-cleanup", undefined, profileName),
586
+ ...Paths.resolveTelegramServiceJournalStorage(
587
+ "thread-cleanup",
588
+ undefined,
589
+ profileName,
590
+ ),
521
591
  profileName,
522
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
592
+ tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({
593
+ botToken,
594
+ }).tokenSha256,
523
595
  });
524
596
  },
525
597
  runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
@@ -553,7 +625,8 @@ export default function (pi: Pi.ExtensionAPI) {
553
625
  instanceId: telegramInstanceId,
554
626
  listRecords: threadStore.list,
555
627
  listWorkspaceBindings: threadStore.listWorkspaceBindings,
556
- getFollowerDisplayTitle: telegramBusFollowerRegistrationState.getDisplayTitle,
628
+ getFollowerDisplayTitle:
629
+ telegramBusFollowerRegistrationState.getDisplayTitle,
557
630
  getActiveTurnTarget: activeTurnRuntime.getTarget,
558
631
  getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
559
632
  isFollowerRegistered: telegramBusFollowerRegistrationState.isRegistered,
@@ -599,7 +672,9 @@ export default function (pi: Pi.ExtensionAPI) {
599
672
  registry: telegramJournalReferenceRegistry,
600
673
  resolveBinding: resolveTelegramUpdateJournalBinding,
601
674
  referenceClass: "polling-cursor",
602
- operation(binding) { return binding.journal.read().acceptedThroughUpdateId; },
675
+ operation(binding) {
676
+ return binding.journal.read().acceptedThroughUpdateId;
677
+ },
603
678
  });
604
679
  },
605
680
  getActiveSourceMessageIds: activeTurnRuntime.getSourceMessageIds,
@@ -652,7 +727,10 @@ export default function (pi: Pi.ExtensionAPI) {
652
727
  getBotToken: configStore.getBotToken,
653
728
  getBotScope() {
654
729
  const config = configStore.get();
655
- return config.botUsername ?? (config.botId === undefined ? undefined : String(config.botId));
730
+ return (
731
+ config.botUsername ??
732
+ (config.botId === undefined ? undefined : String(config.botId))
733
+ );
656
734
  },
657
735
  recordRuntimeEvent,
658
736
  targetActivity: telegramApiTargetActivityRuntime,
@@ -686,8 +764,9 @@ export default function (pi: Pi.ExtensionAPI) {
686
764
  telegramBusFollowerRegistrationState.waitForGeneration,
687
765
  getForwardCommentBatchPosition:
688
766
  textGroupRuntime.getPreparedForwardingPosition,
689
- validateForwardOwnership:
690
- Bus.createTelegramBusForwardOwnershipValidator(telegramBusFollowerRegistry),
767
+ validateForwardOwnership: Bus.createTelegramBusForwardOwnershipValidator(
768
+ telegramBusFollowerRegistry,
769
+ ),
691
770
  recordRuntimeEvent,
692
771
  });
693
772
  const telegramApiRuntime = BusApi.createTelegramBusAwareApiRuntime({
@@ -733,11 +812,12 @@ export default function (pi: Pi.ExtensionAPI) {
733
812
  editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
734
813
  });
735
814
  // Rotate the guest placeholder frames until the final replacement stops them.
736
- const guestPlaceholderRuntime =
737
- Replies.createTelegramGuestPlaceholderRuntime({
815
+ const guestPlaceholderRuntime = Replies.createTelegramGuestPlaceholderRuntime(
816
+ {
738
817
  editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
739
818
  recordRuntimeEvent,
740
- });
819
+ },
820
+ );
741
821
 
742
822
  const promptDispatchRuntime = Runtime.createTelegramPromptDispatchRuntime({
743
823
  lifecycle,
@@ -751,7 +831,8 @@ export default function (pi: Pi.ExtensionAPI) {
751
831
  const epoch = getCurrentLeaderEpoch();
752
832
  return epoch === undefined ? undefined : `direct:${epoch}`;
753
833
  }
754
- if (!telegramBusFollowerRegistrationState.isRegistered()) return undefined;
834
+ if (!telegramBusFollowerRegistrationState.isRegistered())
835
+ return undefined;
755
836
  const generation = telegramBusFollowerRegistrationState.getGeneration();
756
837
  return generation ? `follower:${generation}` : undefined;
757
838
  },
@@ -952,15 +1033,21 @@ export default function (pi: Pi.ExtensionAPI) {
952
1033
  getCommands,
953
1034
  getReservedCommandNames: Commands.getTelegramReservedCommandNames,
954
1035
  });
955
- const menuActions = Menu.createTelegramMenuActionRuntimeWithStateBuilder({
1036
+ const getQueueMenuState = Menu.createTelegramModelMenuStateBuilder({
956
1037
  runtime: modelMenuRuntime,
957
1038
  createSettingsManager: Pi.createSettingsManager,
958
1039
  getActiveModel: currentModelRuntime.get,
1040
+ });
1041
+ const menuActionPorts = {
1042
+ getModelMenuState: getQueueMenuState,
1043
+ getActiveModel: currentModelRuntime.get,
959
1044
  getThinkingLevel,
960
1045
  getQueueItemCount,
961
1046
  getPendingCancellationCount() {
962
- return getCurrentLeaderEpoch() === undefined ? 0 :
963
- updateAdmissionRuntimeBinding.getActive()?.getState()?.abandoningClaimCount ?? 0;
1047
+ return getCurrentLeaderEpoch() === undefined
1048
+ ? 0
1049
+ : (updateAdmissionRuntimeBinding.getActive()?.getState()
1050
+ ?.abandoningClaimCount ?? 0);
964
1051
  },
965
1052
  buildStatusHtml: Commands.createTelegramAppMenuHtmlBuilder({
966
1053
  buildStatusHtml: Status.createTelegramStatusHtmlBuilder({
@@ -973,9 +1060,6 @@ export default function (pi: Pi.ExtensionAPI) {
973
1060
  storeModelMenuState: modelMenuRuntime.storeState,
974
1061
  isIdle,
975
1062
  canOfferInFlightModelSwitch: modelSwitchController.canOfferInFlightSwitch,
976
- sendTextReply,
977
- editInteractiveMessage,
978
- sendInteractiveMessage,
979
1063
  sectionRegistry,
980
1064
 
981
1065
  // Menu/status UI uses this to reflect whether the active Telegram turn expects voice delivery.
@@ -983,15 +1067,15 @@ export default function (pi: Pi.ExtensionAPI) {
983
1067
  const turn = activeTurnRuntime.get();
984
1068
  return Voice.isVoiceTurn(turn);
985
1069
  },
1070
+ };
1071
+ const menuActions = Menu.createTelegramMenuActionRuntime({
1072
+ ...menuActionPorts,
1073
+ sendTextReply,
1074
+ editInteractiveMessage,
1075
+ sendInteractiveMessage,
986
1076
  });
987
1077
 
988
1078
  // --- Queue And Settings Menus ---
989
-
990
- const getQueueMenuState = Menu.createTelegramModelMenuStateBuilder({
991
- runtime: modelMenuRuntime,
992
- createSettingsManager: Pi.createSettingsManager,
993
- getActiveModel: currentModelRuntime.get,
994
- });
995
1079
  const queueMenuRuntime = MenuQueue.createTelegramQueueMenuRuntime({
996
1080
  telegramQueueStore,
997
1081
  queueMutationRuntime,
@@ -1008,20 +1092,32 @@ export default function (pi: Pi.ExtensionAPI) {
1008
1092
  const threadDisplaySettingsRuntime =
1009
1093
  ThreadDisplay.createTelegramThreadDisplaySettingsRuntime({
1010
1094
  getTarget() {
1011
- return telegramBusFollowerRegistrationState.getTarget() ??
1012
- telegramBusLeaderState.getTarget();
1095
+ return (
1096
+ telegramBusFollowerRegistrationState.getTarget() ??
1097
+ telegramBusLeaderState.getTarget()
1098
+ );
1099
+ },
1100
+ getBinding(target) {
1101
+ return threadStore.getWorkspaceBindingByTarget(target);
1013
1102
  },
1014
- getBinding(target) { return threadStore.getWorkspaceBindingByTarget(target); },
1015
1103
  apply(mode) {
1016
1104
  return ThreadDisplay.applyTelegramThreadDisplaySetting(mode, {
1017
1105
  getProfileKey: configStore.getActiveProfileName,
1018
- ownsLeader() { return getCurrentLeaderEpoch() !== undefined; },
1019
- getLeaderSetter() { return telegramBusLeaderRuntime.setThreadDisplayMode; },
1020
- getFollowerSetter() { return telegramBusFollowerRegistration.setThreadDisplayMode; },
1106
+ ownsLeader() {
1107
+ return getCurrentLeaderEpoch() !== undefined;
1108
+ },
1109
+ getLeaderSetter() {
1110
+ return telegramBusLeaderRuntime.setThreadDisplayMode;
1111
+ },
1112
+ getFollowerSetter() {
1113
+ return telegramBusFollowerRegistration.setThreadDisplayMode;
1114
+ },
1021
1115
  reloadConfig: configStore.load,
1022
1116
  });
1023
1117
  },
1024
- reset(target) { return telegramThreadDisplayNameResetBinding.reset(target); },
1118
+ reset(target) {
1119
+ return telegramThreadDisplayNameResetBinding.reset(target);
1120
+ },
1025
1121
  });
1026
1122
  const settingsMenuRuntime = MenuSettings.createTelegramSettingsMenuRuntime(
1027
1123
  {
@@ -1036,12 +1132,14 @@ export default function (pi: Pi.ExtensionAPI) {
1036
1132
  reviewInactiveThreads: inactiveThreadCleanupReviewRuntime.review,
1037
1133
  getThreadDisplayMode() {
1038
1134
  return threadStore.getBotState().threadMode === "enabled"
1039
- ? Config.resolveTelegramThreadDisplayMode(configStore.get()) : undefined;
1135
+ ? Config.resolveTelegramThreadDisplayMode(configStore.get())
1136
+ : undefined;
1040
1137
  },
1041
1138
  isThreadDisplayCustom: threadDisplaySettingsRuntime.isCustom,
1042
1139
  async setThreadDisplayMode(mode) {
1043
- try { await threadDisplaySettingsRuntime.setMode(mode); }
1044
- catch (error) {
1140
+ try {
1141
+ await threadDisplaySettingsRuntime.setMode(mode);
1142
+ } catch (error) {
1045
1143
  recordRuntimeEvent("bus", error, { phase: "thread-display-setting" });
1046
1144
  throw error;
1047
1145
  }
@@ -1054,12 +1152,59 @@ export default function (pi: Pi.ExtensionAPI) {
1054
1152
 
1055
1153
  const foreignOwnedUpdateForwarder =
1056
1154
  telegramBusFollowerClients.foreignOwnedUpdateForwarder;
1057
- const workspaceRestoreFollowerController = Bus.createTelegramBusWorkspaceRestoreController({
1058
- getFollower: telegramBusFollowerRegistry.get,
1059
- localProtocolIdentity: telegramBusProtocolIdentity,
1060
- createRequestId: telegramBusFollowerClients.createRequestId,
1061
- getAuthSecret() { return telegramBusAuthSecret; },
1062
- });
1155
+ const workspaceRestoreFollowerController =
1156
+ Bus.createTelegramBusWorkspaceRestoreController({
1157
+ getFollower: telegramBusFollowerRegistry.get,
1158
+ localProtocolIdentity: telegramBusProtocolIdentity,
1159
+ createRequestId: telegramBusFollowerClients.createRequestId,
1160
+ getAuthSecret() {
1161
+ return telegramBusAuthSecret;
1162
+ },
1163
+ });
1164
+ const liveRebindFollowerController =
1165
+ Bus.createTelegramBusLiveRebindController({
1166
+ getFollower: telegramBusFollowerRegistry.get,
1167
+ localProtocolIdentity: telegramBusProtocolIdentity,
1168
+ createRequestId: telegramBusFollowerClients.createRequestId,
1169
+ getAuthSecret() {
1170
+ return telegramBusAuthSecret;
1171
+ },
1172
+ });
1173
+ const liveFollowerPorts = {
1174
+ getJournalBindingKey(follower: Bus.TelegramBusFollowerView) {
1175
+ if (!follower.profileKey || !follower.sessionId) return undefined;
1176
+ return telegramJournalBindingRuntime.createRecipientResolver(
1177
+ follower.profileKey,
1178
+ follower.sessionId,
1179
+ )()?.recoveryKey;
1180
+ },
1181
+ isSelectedCommandAvailable(
1182
+ follower: Readonly<Bus.TelegramBusFollowerView>,
1183
+ name: string,
1184
+ ): boolean {
1185
+ return (
1186
+ Bus.hasTelegramBusSharedCapabilities(
1187
+ telegramBusProtocolIdentity,
1188
+ follower.protocol,
1189
+ [
1190
+ ...Bus.TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES,
1191
+ ...Bus.TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES,
1192
+ ],
1193
+ ) &&
1194
+ typeof selectedMenuDelivery === "function" &&
1195
+ typeof inboundRouteRuntime.prepareHeldCommand === "function" &&
1196
+ typeof inboundRouteRuntime.canPrepareHeldCommand === "function" &&
1197
+ inboundRouteRuntime.canPrepareHeldCommand(name, {
1198
+ showStatus: menuActions.sendStatusMessage,
1199
+ sendTextReply,
1200
+ }) &&
1201
+ !!captureTelegramStateAuthority() &&
1202
+ !!workspaceAdmissionRuntime.resolve() &&
1203
+ !!resolveWorkspaceRestoreStore()
1204
+ );
1205
+ },
1206
+ run: liveRebindFollowerController,
1207
+ };
1063
1208
  const observedThreadTargetBinding =
1064
1209
  Polling.createTelegramThreadTargetObservationBinding<Pi.ExtensionContext>();
1065
1210
  const topicLifecycleSync =
@@ -1093,6 +1238,18 @@ export default function (pi: Pi.ExtensionAPI) {
1093
1238
  Commands.createTelegramThreadDisplayNameRenameBinding();
1094
1239
  const telegramThreadDisplayNameResetBinding =
1095
1240
  Commands.createTelegramThreadDisplayNameResetBinding();
1241
+ const observeLiveTargetWork = Bindings.createTelegramLiveTargetWorkObserver({
1242
+ queue: telegramQueueStore,
1243
+ activeTurn: activeTurnRuntime,
1244
+ lifecycle: bridgeRuntime.lifecycle,
1245
+ isIdle,
1246
+ hasPendingMessages,
1247
+ hasPendingControl: pendingModelSwitchStore.has,
1248
+ publication: publicationRuntime,
1249
+ activity: activityRuntime,
1250
+ delivery: deliveryLifecycleRuntime,
1251
+ api: telegramApiTargetActivityRuntime,
1252
+ });
1096
1253
  const inboundRouteRuntime = Routing.createTelegramInboundRouteRuntime({
1097
1254
  configStore,
1098
1255
  callApi: callTelegramApi,
@@ -1101,6 +1258,7 @@ export default function (pi: Pi.ExtensionAPI) {
1101
1258
  },
1102
1259
  getAdmissionScope: getTelegramUpdateAdmissionScope,
1103
1260
  getAdmissionJournalBinding: getTelegramQueueJournalBinding,
1261
+ beginCommandEffectWork: publicationRuntime.beginWork,
1104
1262
  getMessageOwnership: messageOwnershipRuntime.getForwardOwnership,
1105
1263
  recordMessageOwnership: messageOwnershipRuntime.recordRouted,
1106
1264
  ...inboundBusProjectionRuntime,
@@ -1118,86 +1276,201 @@ export default function (pi: Pi.ExtensionAPI) {
1118
1276
  getSessionId: Pi.getExtensionContextSessionId,
1119
1277
  getCwd: Pi.getExtensionContextCwd,
1120
1278
  getLeaderIdentity: telegramBusLeaderState.getIdentity,
1279
+ observeLeaderWork: observeLiveTargetWork,
1121
1280
  followerRegistry: telegramBusFollowerRegistry,
1122
1281
  runFollower: workspaceRestoreFollowerController,
1282
+ liveFollower: liveFollowerPorts,
1123
1283
  },
1124
1284
  getWorkspaceRestoreStore: resolveWorkspaceRestoreStore,
1125
1285
  captureWorkspaceExternalProtection,
1126
1286
  inspectRestoreSourceCompletion(expected) {
1127
- return telegramJournalReferenceRegistry.withReference({ referenceClass: "operator-disposition",
1128
- recoveryKey: expected.journalBindingKey }, function () {
1129
- const observed = telegramJournalBindingRuntime.inspectSourceCompletion(expected.journalBindingKey, { updateId: expected.updateId,
1130
- sourceSha256: expected.sourceSha256, completionSha256: expected.completionSha256 });
1131
- return observed && { journalBindingKey: expected.journalBindingKey, ...observed };
1132
- });
1287
+ return telegramJournalReferenceRegistry.withReference(
1288
+ {
1289
+ referenceClass: "operator-disposition",
1290
+ recoveryKey: expected.journalBindingKey,
1291
+ },
1292
+ function () {
1293
+ const observed =
1294
+ telegramJournalBindingRuntime.inspectSourceCompletion(
1295
+ expected.journalBindingKey,
1296
+ {
1297
+ updateId: expected.updateId,
1298
+ sourceSha256: expected.sourceSha256,
1299
+ completionSha256: expected.completionSha256,
1300
+ },
1301
+ );
1302
+ return (
1303
+ observed && {
1304
+ journalBindingKey: expected.journalBindingKey,
1305
+ ...observed,
1306
+ }
1307
+ );
1308
+ },
1309
+ );
1133
1310
  },
1134
1311
  inspectRestoreQueuedReceipt(expected) {
1135
- return telegramJournalReferenceRegistry.withReference({ referenceClass: "operator-disposition",
1136
- recoveryKey: expected.journalBindingKey }, function () {
1137
- return telegramJournalBindingRuntime.inspectQueuedReceipt(expected.journalBindingKey, { queueKind: expected.queueKind, receiptId: expected.receiptId,
1138
- sourceUpdateIds: [...expected.sourceUpdateIds], queueOwner: { ...expected.queueOwner } });
1139
- });
1312
+ return telegramJournalReferenceRegistry.withReference(
1313
+ {
1314
+ referenceClass: "operator-disposition",
1315
+ recoveryKey: expected.journalBindingKey,
1316
+ },
1317
+ function () {
1318
+ return telegramJournalBindingRuntime.inspectQueuedReceipt(
1319
+ expected.journalBindingKey,
1320
+ {
1321
+ queueKind: expected.queueKind,
1322
+ receiptId: expected.receiptId,
1323
+ sourceUpdateIds: [...expected.sourceUpdateIds],
1324
+ queueOwner: { ...expected.queueOwner },
1325
+ },
1326
+ );
1327
+ },
1328
+ );
1140
1329
  },
1141
1330
  inspectRestoreSourceAbandonment(updateId, journalBindingKey) {
1142
- return telegramJournalReferenceRegistry.withReference({
1143
- referenceClass: "operator-disposition",
1144
- recoveryKey: journalBindingKey,
1145
- }, function () {
1146
- return telegramJournalBindingRuntime.inspectSourceAbandonment(journalBindingKey, updateId);
1147
- });
1331
+ return telegramJournalReferenceRegistry.withReference(
1332
+ {
1333
+ referenceClass: "operator-disposition",
1334
+ recoveryKey: journalBindingKey,
1335
+ },
1336
+ function () {
1337
+ return telegramJournalBindingRuntime.inspectSourceAbandonment(
1338
+ journalBindingKey,
1339
+ updateId,
1340
+ );
1341
+ },
1342
+ );
1148
1343
  },
1149
1344
  inspectRoutingInputGroupExpiry(input) {
1150
- return telegramJournalReferenceRegistry.withReference({ referenceClass: "operator-disposition", recoveryKey: input.journalBindingKey },
1151
- function () { return telegramJournalBindingRuntime.inspectSourceGroupExpiry(input.journalBindingKey, input.updateIds); });
1345
+ return telegramJournalReferenceRegistry.withReference(
1346
+ {
1347
+ referenceClass: "operator-disposition",
1348
+ recoveryKey: input.journalBindingKey,
1349
+ },
1350
+ function () {
1351
+ return telegramJournalBindingRuntime.inspectSourceGroupExpiry(
1352
+ input.journalBindingKey,
1353
+ input.updateIds,
1354
+ );
1355
+ },
1356
+ );
1152
1357
  },
1153
- inspectTemporaryThreadSources(_target, requiredJournalBindingKeys) {
1358
+ inspectTemporaryThreadSources(
1359
+ target,
1360
+ requiredJournalBindingKeys,
1361
+ ownInputs,
1362
+ ) {
1154
1363
  const binding = resolveTelegramUpdateJournalBinding();
1155
1364
  const botToken = configStore.getBotToken();
1156
1365
  if (!binding || !botToken) return undefined;
1157
1366
  const profile = configStore.getActiveProfileName() ?? "default";
1158
- const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({ botToken, botId: getTelegramBotId() });
1367
+ const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({
1368
+ botToken,
1369
+ botId: getTelegramBotId(),
1370
+ });
1159
1371
  return configStore.withSourceSerialization(function () {
1160
1372
  return Journal.isTelegramThreadCleanupJournalNamespaceClear({
1161
- directory: Paths.resolveTelegramTempDir(), profile, botIdentity,
1162
- pollingPath: telegramLeaderJournalPath.resolve(configStore.getActiveProfileName()),
1163
- requiredJournalBindingKeys: [binding.recoveryKey, ...requiredJournalBindingKeys],
1164
- limits: { maxDirectoryEntries: 10_000, maxFiles: 4096, maxBytes: 64 * 1024 * 1024,
1165
- maxEntries: 10_000, maxWork: 100_000 },
1373
+ directory: Paths.resolveTelegramTempDir(),
1374
+ profile,
1375
+ botIdentity,
1376
+ pollingPath: telegramLeaderJournalPath.resolve(
1377
+ configStore.getActiveProfileName(),
1378
+ ),
1379
+ requiredJournalBindingKeys: [
1380
+ binding.recoveryKey,
1381
+ ...requiredJournalBindingKeys,
1382
+ ],
1383
+ ...(target.threadId !== undefined && ownInputs
1384
+ ? {
1385
+ cleanup: {
1386
+ target: { chatId: target.chatId, threadId: target.threadId },
1387
+ ownInputs,
1388
+ },
1389
+ }
1390
+ : {}),
1391
+ limits: {
1392
+ maxDirectoryEntries: 10_000,
1393
+ maxFiles: 4096,
1394
+ maxBytes: 64 * 1024 * 1024,
1395
+ maxEntries: 10_000,
1396
+ maxWork: 100_000,
1397
+ },
1166
1398
  withSourceReference(path, operation) {
1167
- return telegramJournalReferenceRegistry.withReference({ referenceClass: "operator-disposition",
1168
- recoveryKey: Journal.createTelegramUpdateJournalBindingKey({ path, profileName: profile, botIdentity }) }, operation);
1399
+ return telegramJournalReferenceRegistry.withReference(
1400
+ {
1401
+ referenceClass: "operator-disposition",
1402
+ recoveryKey: Journal.createTelegramUpdateJournalBindingKey({
1403
+ path,
1404
+ profileName: profile,
1405
+ botIdentity,
1406
+ }),
1407
+ },
1408
+ operation,
1409
+ );
1169
1410
  },
1170
- }) ? [] : undefined;
1411
+ })
1412
+ ? []
1413
+ : undefined;
1171
1414
  });
1172
1415
  },
1173
1416
  hasWorkspaceRestoreAuthority() {
1174
- return lockRuntime.owns() && Bus.hasTelegramBusCapability(telegramBusProtocolIdentity, Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE);
1417
+ return (
1418
+ lockRuntime.owns() &&
1419
+ Bus.hasTelegramBusCapability(
1420
+ telegramBusProtocolIdentity,
1421
+ Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE,
1422
+ )
1423
+ );
1424
+ },
1425
+ hasWorkspaceLiveRebindAuthority() {
1426
+ return (
1427
+ lockRuntime.owns() &&
1428
+ Bus.hasTelegramBusCapability(
1429
+ telegramBusProtocolIdentity,
1430
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
1431
+ ) &&
1432
+ Bus.hasTelegramBusCapability(
1433
+ telegramBusProtocolIdentity,
1434
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
1435
+ ) &&
1436
+ Bus.hasTelegramBusCapability(
1437
+ telegramBusProtocolIdentity,
1438
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
1439
+ )
1440
+ );
1175
1441
  },
1176
1442
  getSessionGeneration: telegramSessionContextStore.getGeneration,
1177
1443
  bridgeRuntime,
1178
1444
  requestNewSession(source) {
1179
- const updateId = Updates.getTelegramUpdateExecutionFence(source)?.updateId;
1445
+ const updateId =
1446
+ Updates.getTelegramUpdateExecutionFence(source)?.updateId;
1180
1447
  if (updateId === undefined) {
1181
- throw new Error("Telegram session replacement requires durable update authority.");
1448
+ throw new Error(
1449
+ "Telegram session replacement requires durable update authority.",
1450
+ );
1182
1451
  }
1183
- const callbackMessage = source && typeof source === "object" && "message" in source
1184
- ? (source as { message?: unknown }).message
1185
- : source;
1186
- const messageTarget = callbackMessage as {
1187
- chat?: { id?: unknown };
1188
- message_id?: unknown;
1189
- message_thread_id?: unknown;
1190
- } | undefined;
1191
- const target = typeof messageTarget?.chat?.id === "number" &&
1192
- typeof messageTarget.message_id === "number"
1193
- ? {
1194
- chatId: messageTarget.chat.id,
1195
- messageId: messageTarget.message_id,
1196
- ...(typeof messageTarget.message_thread_id === "number"
1197
- ? { threadId: messageTarget.message_thread_id }
1198
- : {}),
1452
+ const callbackMessage =
1453
+ source && typeof source === "object" && "message" in source
1454
+ ? (source as { message?: unknown }).message
1455
+ : source;
1456
+ const messageTarget = callbackMessage as
1457
+ | {
1458
+ chat?: { id?: unknown };
1459
+ message_id?: unknown;
1460
+ message_thread_id?: unknown;
1199
1461
  }
1200
- : undefined;
1462
+ | undefined;
1463
+ const target =
1464
+ typeof messageTarget?.chat?.id === "number" &&
1465
+ typeof messageTarget.message_id === "number"
1466
+ ? {
1467
+ chatId: messageTarget.chat.id,
1468
+ messageId: messageTarget.message_id,
1469
+ ...(typeof messageTarget.message_thread_id === "number"
1470
+ ? { threadId: messageTarget.message_thread_id }
1471
+ : {}),
1472
+ }
1473
+ : undefined;
1201
1474
  if (!target) {
1202
1475
  throw new Error("Telegram session replacement target is unavailable.");
1203
1476
  }
@@ -1214,6 +1487,7 @@ export default function (pi: Pi.ExtensionAPI) {
1214
1487
  currentModelRuntime,
1215
1488
  modelSwitchController,
1216
1489
  menuActions,
1490
+ isVoiceReplyActive: menuActionPorts.isVoiceReplyActive,
1217
1491
  updateSettingsMenuMessage: settingsMenuRuntime.updateSettingsMenuMessage,
1218
1492
  openQueueMenu: queueMenuRuntime.openQueueMenu,
1219
1493
  queueMenuCallbackHandler: queueMenuRuntime.handleCallbackQuery,
@@ -1226,6 +1500,7 @@ export default function (pi: Pi.ExtensionAPI) {
1226
1500
  inboundHandlerRuntime,
1227
1501
  threadStore,
1228
1502
  runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1503
+ liveRebindCleanupSchedule: Routing.TELEGRAM_LIVE_REBIND_CLEANUP_SCHEDULE,
1229
1504
  updateStatus,
1230
1505
  isContextActive: telegramSessionContextStore.isCurrent,
1231
1506
  dispatchNextQueuedTelegramTurn,
@@ -1246,6 +1521,24 @@ export default function (pi: Pi.ExtensionAPI) {
1246
1521
  startGuestPlaceholder: guestPlaceholderRuntime.start,
1247
1522
  sendTextReply,
1248
1523
  setMyCommands,
1524
+ captureThreadNameRecipientAuthority(target, ctx) {
1525
+ // Ordinary follower/classic dialogs keep their unguarded policy; closures are leader-local only.
1526
+ if (
1527
+ telegramBusFollowerRegistrationState.isRegistered() ||
1528
+ target.threadId === undefined
1529
+ )
1530
+ return undefined;
1531
+ return Threads.createTelegramWorkspaceThreadRenameRecipient({
1532
+ store: threadStore,
1533
+ instanceId: telegramInstanceId,
1534
+ target,
1535
+ assertAuthority() {
1536
+ if (telegramSessionContextStore.get() !== ctx)
1537
+ throw new Error("Telegram Thread name dialog lost Pi context.");
1538
+ },
1539
+ getAuthority: threadNameRecipientAuthority.getAuthority,
1540
+ }).assertAuthority;
1541
+ },
1249
1542
  validateThreadName(threadName) {
1250
1543
  return ThreadNaming.getTelegramManualThreadDisplayNameValidationError(
1251
1544
  threadName,
@@ -1300,7 +1593,8 @@ export default function (pi: Pi.ExtensionAPI) {
1300
1593
  runtimeBinding: updateAdmissionRuntimeBinding,
1301
1594
  acquireSourceReference(role, binding) {
1302
1595
  return telegramJournalReferenceRegistry.acquire({
1303
- referenceClass: role === "leader" ? "leader-lifecycle" : "follower-lifecycle",
1596
+ referenceClass:
1597
+ role === "leader" ? "leader-lifecycle" : "follower-lifecycle",
1304
1598
  recoveryKey: binding.recoveryKey,
1305
1599
  });
1306
1600
  },
@@ -1321,28 +1615,46 @@ export default function (pi: Pi.ExtensionAPI) {
1321
1615
  },
1322
1616
  worker: {
1323
1617
  expireRoutingInput: inboundRouteRuntime.expireRoutingInput,
1324
- beforeQueueReceiptPublished: inboundRouteRuntime.beforeQueueReceiptPublished,
1618
+ beforeQueueReceiptPublished:
1619
+ inboundRouteRuntime.beforeQueueReceiptPublished,
1325
1620
  onQueueReceiptCompleted: inboundRouteRuntime.onQueueReceiptCompleted,
1326
1621
  defaultHandle: inboundRouteRuntime.handleUpdate,
1327
1622
  async shouldReviewHistoricalInput(entry, ctx, signal) {
1328
1623
  if (signal.aborted || !telegramSessionContextStore.isCurrent(ctx)) {
1329
1624
  throw new Error("Workspace Restore source generation ended.");
1330
1625
  }
1331
- const verdict = await inboundRouteRuntime.shouldReviewHistoricalInput(entry, ctx, signal);
1626
+ const verdict = await inboundRouteRuntime.shouldReviewHistoricalInput(
1627
+ entry,
1628
+ ctx,
1629
+ signal,
1630
+ );
1332
1631
  if (signal.aborted || !telegramSessionContextStore.isCurrent(ctx)) {
1333
1632
  throw new Error("Workspace Restore source generation ended.");
1334
1633
  }
1335
1634
  if (verdict === "retain") return verdict;
1336
1635
  const journalBindingKey = getTelegramQueueJournalBinding();
1337
- if (journalBindingKey && resolveWorkspaceRestoreStore()?.list().some(function (intent) {
1338
- return intent.request.source.journalBindingKey === journalBindingKey &&
1339
- intent.request.source.updateIds.includes(entry.updateId);
1340
- })) return true;
1636
+ if (
1637
+ journalBindingKey &&
1638
+ resolveWorkspaceRestoreStore()
1639
+ ?.list()
1640
+ .some(function (intent) {
1641
+ return (
1642
+ intent.request.source.journalBindingKey === journalBindingKey &&
1643
+ intent.request.source.updateIds.includes(entry.updateId)
1644
+ );
1645
+ })
1646
+ )
1647
+ return true;
1341
1648
  return verdict;
1342
1649
  },
1343
1650
  spendHistoricalInput: true,
1344
1651
  onHeldSourcesPrepared(input): Promise<void> {
1345
- return inboundRouteRuntime.forgetPreviousWorld(input, lockedPollingRuntime.captureTransportAuthority).then(function () {});
1652
+ return inboundRouteRuntime
1653
+ .forgetPreviousWorld(
1654
+ input,
1655
+ lockedPollingRuntime.captureTransportAuthority,
1656
+ )
1657
+ .then(function () {});
1346
1658
  },
1347
1659
  async shouldHoldPendingInput(entry, ctx, signal) {
1348
1660
  if (signal.aborted || !telegramSessionContextStore.isCurrent(ctx)) {
@@ -1367,9 +1679,11 @@ export default function (pi: Pi.ExtensionAPI) {
1367
1679
  isRegistered: telegramBusFollowerRegistrationState.isRegistered,
1368
1680
  getGeneration: telegramBusFollowerRegistrationState.getGeneration,
1369
1681
  prepareBinding(binding) {
1370
- telegramJournalBindingRuntime.prepareActiveFollowerSuccession(function isCurrent() {
1371
- return binding.hasAuthority?.() === true;
1372
- });
1682
+ telegramJournalBindingRuntime.prepareActiveFollowerSuccession(
1683
+ function () {
1684
+ return binding.hasAuthority?.() === true;
1685
+ },
1686
+ );
1373
1687
  },
1374
1688
  prepareUpdateForExecution(update) {
1375
1689
  return BusFollower.prepareTelegramBusFollowerJournaledUpdateForExecution(
@@ -1450,32 +1764,159 @@ export default function (pi: Pi.ExtensionAPI) {
1450
1764
  local: agentMessageRuntime,
1451
1765
  follower: telegramBusFollowerClients.agentMessages,
1452
1766
  });
1453
- const followerWorkspaceRestore = BusFollower.createTelegramBusFollowerWorkspaceRestoreHandler({
1767
+ const followerWorkspaceTargetContextPorts = {
1768
+ isContextCurrent: telegramSessionContextStore.isCurrent,
1769
+ getSessionId: Pi.getExtensionContextSessionId,
1770
+ getCwd: Pi.getExtensionContextCwd,
1771
+ getGeneration: telegramSessionContextStore.getGeneration,
1772
+ getProfileBindingKey() {
1773
+ return workspaceAdmissionRuntime.resolve()?.getProfileKey();
1774
+ },
1775
+ getOperatorUserId: configStore.getAllowedUserId,
1776
+ getLeaderState: lockRuntime.getState,
1777
+ getAuthenticatedSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1778
+ getLeaderProtocol: telegramBusFollowerRegistrationState.getLeaderProtocol,
1779
+ };
1780
+ const followerWorkspaceTargetOwnerPorts = {
1454
1781
  instanceId: telegramInstanceId,
1455
- getContextAuthority: BusFollower.createTelegramBusFollowerRestoreContextGetter({
1456
- isContextCurrent: telegramSessionContextStore.isCurrent,
1457
- getSessionId: Pi.getExtensionContextSessionId,
1458
- getCwd: Pi.getExtensionContextCwd,
1459
- getGeneration: telegramSessionContextStore.getGeneration,
1460
- getProfileBindingKey() {
1461
- return workspaceAdmissionRuntime.resolve()?.getProfileKey();
1462
- },
1463
- getOperatorUserId: configStore.getAllowedUserId,
1464
- getLeaderState: lockRuntime.getState,
1465
- getAuthenticatedSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1466
- getLeaderProtocol: telegramBusFollowerRegistrationState.getLeaderProtocol,
1467
- }),
1468
- readRestoreIntent(operationId, profileBindingKey) {
1469
- if (profileBindingKey !== workspaceAdmissionRuntime.resolve()?.getProfileKey()) return undefined;
1470
- return resolveWorkspaceRestoreStore()?.list().find(function (intent) {
1471
- return intent.request.operationId === operationId;
1472
- });
1782
+ readRestoreIntent(operationId: string, profileBindingKey: string) {
1783
+ if (
1784
+ profileBindingKey !==
1785
+ workspaceAdmissionRuntime.resolve()?.getProfileKey()
1786
+ )
1787
+ return undefined;
1788
+ return resolveWorkspaceRestoreStore()
1789
+ ?.list()
1790
+ .find(function (intent) {
1791
+ return intent.request.operationId === operationId;
1792
+ });
1793
+ },
1794
+ readLiveRebindIntent(operationId: string, profileBindingKey: string) {
1795
+ if (
1796
+ profileBindingKey !==
1797
+ workspaceAdmissionRuntime.resolve()?.getProfileKey()
1798
+ )
1799
+ return undefined;
1800
+ return resolveWorkspaceRestoreStore()
1801
+ ?.listLiveRebindings()
1802
+ .find(function (intent) {
1803
+ return intent.request.operationId === operationId;
1804
+ });
1473
1805
  },
1474
1806
  topicTargetStore: threadStore,
1475
1807
  registrationState: telegramBusFollowerRegistrationState,
1476
1808
  getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
1477
1809
  recordRuntimeEvent,
1478
- });
1810
+ };
1811
+ const followerWorkspaceRestore =
1812
+ BusFollower.createTelegramBusFollowerWorkspaceRestoreHandler({
1813
+ ...followerWorkspaceTargetOwnerPorts,
1814
+ getContextAuthority:
1815
+ BusFollower.createTelegramBusFollowerRestoreContextGetter(
1816
+ followerWorkspaceTargetContextPorts,
1817
+ ),
1818
+ });
1819
+ const followerLiveRebindTarget =
1820
+ BusFollower.createTelegramBusFollowerWorkspaceRestoreHandler({
1821
+ ...followerWorkspaceTargetOwnerPorts,
1822
+ getContextAuthority:
1823
+ BusFollower.createTelegramBusFollowerRestoreContextGetter({
1824
+ ...followerWorkspaceTargetContextPorts,
1825
+ capability: Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
1826
+ }),
1827
+ });
1828
+ const selectedMenuCaller =
1829
+ BusFollower.createTelegramBusFollowerSelectedMenuCaller({
1830
+ client: {
1831
+ socketPath: getTelegramBusSocketPath,
1832
+ instanceId: telegramInstanceId,
1833
+ createRequestId: telegramBusFollowerClients.createRequestId,
1834
+ getAuthSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1835
+ getRegistrationGeneration:
1836
+ telegramBusFollowerRegistrationState.getGeneration,
1837
+ },
1838
+ protocolIdentity: telegramBusProtocolIdentity,
1839
+ recipient: {
1840
+ getContextAuthority:
1841
+ BusFollower.createTelegramBusFollowerRestoreContextGetter({
1842
+ ...followerWorkspaceTargetContextPorts,
1843
+ // Wire recipient identity is the registered profile key, not the admission namespace.
1844
+ getProfileBindingKey: getTelegramManualFollowerProfileKey,
1845
+ capability: Bus.TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
1846
+ }),
1847
+ getJournalBindingKey:
1848
+ followerAdmissionLifecycleRuntime.getJournalBindingKey,
1849
+ getProcessIdentity() {
1850
+ return {
1851
+ processId: telegramProcessId,
1852
+ processBirthId: telegramQueueProcessBirthId,
1853
+ };
1854
+ },
1855
+ registrationState: telegramBusFollowerRegistrationState,
1856
+ },
1857
+ });
1858
+ const followerLiveRebindRuntime =
1859
+ BusFollower.createTelegramBusFollowerLiveRebindRuntime({
1860
+ getAdmission(ctx: Pi.ExtensionContext) {
1861
+ return telegramSessionContextStore.isCurrent(ctx)
1862
+ ? followerAdmissionLifecycleRuntime
1863
+ : undefined;
1864
+ },
1865
+ applyTarget: followerLiveRebindTarget,
1866
+ getCommandOwner(input, ctx) {
1867
+ if (
1868
+ !Bus.hasTelegramBusCapability(
1869
+ telegramBusProtocolIdentity,
1870
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
1871
+ ) ||
1872
+ !Bus.hasTelegramBusCapability(
1873
+ telegramBusProtocolIdentity,
1874
+ Bus.TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
1875
+ ) ||
1876
+ !input.selectedCommand ||
1877
+ !telegramSessionContextStore.isCurrent(ctx) ||
1878
+ followerAdmissionLifecycleRuntime.getJournalBindingKey() !==
1879
+ input.recipientBindingKey
1880
+ )
1881
+ return undefined;
1882
+ const recipient = followerLiveRebindTarget.prepareLiveCommandRecipient(
1883
+ {
1884
+ operationId: input.operationId,
1885
+ registrationGeneration: input.recipientRegistrationGeneration,
1886
+ sessionId: input.recipientSessionId,
1887
+ sourceUpdateIds: input.updates.map(function sourceUpdateId(update) {
1888
+ return update.update_id;
1889
+ }),
1890
+ target: input.selectedCommand.target,
1891
+ },
1892
+ ctx,
1893
+ );
1894
+ return (
1895
+ recipient &&
1896
+ Bindings.createTelegramFollowerSelectedCommandBinding({
1897
+ ctx,
1898
+ operationId: input.operationId,
1899
+ registrationGeneration: input.recipientRegistrationGeneration,
1900
+ name: input.selectedCommand.name,
1901
+ target: input.selectedCommand.target,
1902
+ recipient,
1903
+ command: inboundRouteRuntime,
1904
+ menu: menuActionPorts,
1905
+ recordOwnership: messageOwnershipRuntime.recordLocal,
1906
+ deliver(effect, assertAuthority) {
1907
+ return selectedMenuCaller({
1908
+ ctx,
1909
+ operationId: input.operationId,
1910
+ registrationGeneration: input.recipientRegistrationGeneration,
1911
+ effect,
1912
+ assertAuthority,
1913
+ });
1914
+ },
1915
+ })
1916
+ );
1917
+ },
1918
+ observeWork: observeLiveTargetWork,
1919
+ });
1479
1920
  const telegramBusFollowerAssembly: BusFollower.TelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext> =
1480
1921
  BusFollower.createTelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext>({
1481
1922
  instanceId: telegramInstanceId,
@@ -1486,12 +1927,63 @@ export default function (pi: Pi.ExtensionAPI) {
1486
1927
  getContext: telegramSessionContextStore.get,
1487
1928
  getAuthSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1488
1929
  getRecipientBindingKey: getTelegramManualFollowerProfileKey,
1930
+ getLiveRebindJournalBindingKey:
1931
+ followerAdmissionLifecycleRuntime.getJournalBindingKey,
1932
+ getSessionId() {
1933
+ return telegramBusFollowerAssembly.getReadySessionId();
1934
+ },
1935
+ getLeaderProtocol:
1936
+ telegramBusFollowerRegistrationState.getLeaderProtocol,
1937
+ getLocalProtocol() {
1938
+ return telegramBusProtocolIdentity;
1939
+ },
1940
+ isLiveRebindSaveEnabled() {
1941
+ return Bus.hasTelegramBusCapability(
1942
+ telegramBusProtocolIdentity,
1943
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
1944
+ );
1945
+ },
1946
+ isLiveRebindApplyEnabled() {
1947
+ return Bus.hasTelegramBusCapability(
1948
+ telegramBusProtocolIdentity,
1949
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
1950
+ );
1951
+ },
1952
+ isLiveRebindSettleEnabled() {
1953
+ return Bus.hasTelegramBusCapability(
1954
+ telegramBusProtocolIdentity,
1955
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
1956
+ );
1957
+ },
1958
+ isLiveRebindCommandSetEnabled() {
1959
+ return (
1960
+ Bus.hasTelegramBusCapability(
1961
+ telegramBusProtocolIdentity,
1962
+ Bus.TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
1963
+ ) &&
1964
+ Bus.hasTelegramBusCapability(
1965
+ telegramBusProtocolIdentity,
1966
+ Bus.TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
1967
+ )
1968
+ );
1969
+ },
1970
+ handleLiveRebindSave: followerLiveRebindRuntime.save,
1971
+ handleLiveRebindApply: followerLiveRebindRuntime.apply,
1972
+ handleLiveRebindSettle: followerLiveRebindRuntime.settle,
1489
1973
  durableAdmission: followerDurableAdmissionRuntime,
1490
1974
  handleQueueHandoff: acceptStagedQueueHandoff,
1491
1975
  handleWorkspaceRestore: followerWorkspaceRestore,
1492
1976
  isWorkspaceRestoreEnabled() {
1493
- return Bus.hasTelegramBusCapability(telegramBusProtocolIdentity, Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
1494
- Bus.hasTelegramBusCapability(telegramBusFollowerRegistrationState.getLeaderProtocol(), Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE);
1977
+ return (
1978
+ Bus.hasTelegramBusCapability(
1979
+ telegramBusProtocolIdentity,
1980
+ Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE,
1981
+ ) &&
1982
+ Bus.hasTelegramBusCapability(
1983
+ telegramBusFollowerRegistrationState.getLeaderProtocol(),
1984
+ Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE,
1985
+ )
1986
+ );
1495
1987
  },
1496
1988
  },
1497
1989
  recovery: {
@@ -1534,7 +2026,9 @@ export default function (pi: Pi.ExtensionAPI) {
1534
2026
  >({
1535
2027
  state: pollingControllerState,
1536
2028
  canStart(ctx) {
1537
- return telegramSessionContextStore.isCurrent(ctx) && lockRuntime.owns(ctx);
2029
+ return (
2030
+ telegramSessionContextStore.isCurrent(ctx) && lockRuntime.owns(ctx)
2031
+ );
1538
2032
  },
1539
2033
  onPersistentConflict(ctx, count): Promise<void> {
1540
2034
  return lockedPollingRuntime.onPersistentConflict(ctx, count);
@@ -1557,7 +2051,9 @@ export default function (pi: Pi.ExtensionAPI) {
1557
2051
  registry: telegramJournalReferenceRegistry,
1558
2052
  resolveBinding: resolveTelegramUpdateJournalBinding,
1559
2053
  referenceClass: "polling-cursor",
1560
- operation(binding) { return binding.journal.read().acceptedThroughUpdateId; },
2054
+ operation(binding) {
2055
+ return binding.journal.read().acceptedThroughUpdateId;
2056
+ },
1561
2057
  });
1562
2058
  },
1563
2059
  async prepareCursorCutover() {
@@ -1565,30 +2061,37 @@ export default function (pi: Pi.ExtensionAPI) {
1565
2061
  registry: telegramJournalReferenceRegistry,
1566
2062
  resolveBinding: resolveTelegramUpdateJournalBinding,
1567
2063
  referenceClass: "polling-cursor",
1568
- operation(binding) { return Polling.cutOverTelegramPollingCursor({
1569
- getLegacyCursor: configStore.getLegacyPollingCursor,
1570
- readJournal: binding.journal.read,
1571
- publishJournalCursor(acceptedThroughUpdateId) {
1572
- binding.journal.appendBatch([], acceptedThroughUpdateId);
1573
- },
1574
- async removeLegacyCursor() {
1575
- configStore.removeLegacyPollingCursor();
1576
- await persistTelegramConfigWithSync();
1577
- },
1578
- }); },
2064
+ operation(binding) {
2065
+ return Polling.cutOverTelegramPollingCursor({
2066
+ getLegacyCursor: configStore.getLegacyPollingCursor,
2067
+ readJournal: binding.journal.read,
2068
+ publishJournalCursor(acceptedThroughUpdateId) {
2069
+ binding.journal.appendBatch([], acceptedThroughUpdateId);
2070
+ },
2071
+ async removeLegacyCursor() {
2072
+ configStore.removeLegacyPollingCursor();
2073
+ await persistTelegramConfigWithSync();
2074
+ },
2075
+ });
2076
+ },
1579
2077
  });
1580
- if (!cutover) throw new Error("Telegram update journal binding is unavailable.");
2078
+ if (!cutover)
2079
+ throw new Error("Telegram update journal binding is unavailable.");
1581
2080
  await cutover;
1582
2081
  },
1583
2082
  getEntryCount: updateAdmissionLifecycleRuntime.getJournalEntryCount,
1584
2083
  signalWorker: updateAdmissionLifecycleRuntime.signal,
1585
2084
  getBootstrapEntryCount() {
1586
- return Journal.withTelegramResolvedUpdateJournalReference({
1587
- registry: telegramJournalReferenceRegistry,
1588
- resolveBinding: resolveTelegramUpdateJournalBinding,
1589
- referenceClass: "polling-bootstrap",
1590
- operation(binding) { return binding.journal.read().entries.length; },
1591
- }) ?? 0;
2085
+ return (
2086
+ Journal.withTelegramResolvedUpdateJournalReference({
2087
+ registry: telegramJournalReferenceRegistry,
2088
+ resolveBinding: resolveTelegramUpdateJournalBinding,
2089
+ referenceClass: "polling-bootstrap",
2090
+ operation(binding) {
2091
+ return binding.journal.read().entries.length;
2092
+ },
2093
+ }) ?? 0
2094
+ );
1592
2095
  },
1593
2096
  onSessionStart: updateAdmissionLifecycleRuntime.onSessionStart,
1594
2097
  },
@@ -1600,17 +2103,67 @@ export default function (pi: Pi.ExtensionAPI) {
1600
2103
  const authorizeFollowerApiCall = Bus.createTelegramFollowerApiCallAuthorizer({
1601
2104
  isMessageOwned: messageOwnershipRuntime.isOwnedByFollower,
1602
2105
  });
1603
- const telegramSessionFolderSweeper = Recovery.createTelegramSessionFolderSweeper({
1604
- getSessionsDir: Paths.resolveTelegramSessionsDir,
1605
- getProfileName: configStore.getActiveProfileName,
1606
- getKeptSessionIds() {
1607
- return [
1608
- ...threadStore.listWorkspaceBindings().map(function bindingSession(binding) { return binding.sessionId; }),
1609
- ...telegramBusFollowerRegistry.list().map(function followerSession(follower) { return follower.sessionId; }),
1610
- Pi.getExtensionContextSessionId(telegramSessionContextStore.get()),
1611
- ];
1612
- },
1613
- });
2106
+ const selectedMenuDelivery =
2107
+ BusLeader.createTelegramBusSelectedMenuDeliveryHandler({
2108
+ followerRegistry: telegramBusFollowerRegistry,
2109
+ protocolIdentity: telegramBusProtocolIdentity,
2110
+ workspace: {
2111
+ getScopeKey() {
2112
+ return workspaceAdmissionRuntime.resolve()?.getProfileKey();
2113
+ },
2114
+ captureAuthority() {
2115
+ const isCurrent = captureTelegramStateAuthority(),
2116
+ epoch = lockRuntime.getOwnedLeaderEpoch(),
2117
+ operatorUserId = configStore.getAllowedUserId();
2118
+ return isCurrent &&
2119
+ epoch !== undefined &&
2120
+ operatorUserId !== undefined
2121
+ ? {
2122
+ executor: {
2123
+ instanceId: telegramInstanceId,
2124
+ leaderEpoch: String(epoch),
2125
+ },
2126
+ operatorUserId,
2127
+ isCurrent,
2128
+ }
2129
+ : undefined;
2130
+ },
2131
+ getStore: resolveWorkspaceRestoreStore,
2132
+ threadStore,
2133
+ getJournalBindingKey: liveFollowerPorts.getJournalBindingKey,
2134
+ run: telegramWorkspaceOperationRuntime.run,
2135
+ },
2136
+ api: {
2137
+ runtime: directTelegramApiRuntime,
2138
+ authorize: authorizeFollowerApiCall,
2139
+ record(record, assertCurrent) {
2140
+ assertCurrent();
2141
+ messageOwnershipRuntime.recordFollower(record);
2142
+ assertCurrent();
2143
+ return messageOwnershipRuntime.isOwnedByFollower(record);
2144
+ },
2145
+ },
2146
+ });
2147
+ const telegramSessionFolderSweeper =
2148
+ Recovery.createTelegramSessionFolderSweeper({
2149
+ getSessionsDir: Paths.resolveTelegramSessionsDir,
2150
+ getProfileName: configStore.getActiveProfileName,
2151
+ getKeptSessionIds() {
2152
+ return [
2153
+ ...threadStore
2154
+ .listWorkspaceBindings()
2155
+ .map(function bindingSession(binding) {
2156
+ return binding.sessionId;
2157
+ }),
2158
+ ...telegramBusFollowerRegistry
2159
+ .list()
2160
+ .map(function followerSession(follower) {
2161
+ return follower.sessionId;
2162
+ }),
2163
+ Pi.getExtensionContextSessionId(telegramSessionContextStore.get()),
2164
+ ];
2165
+ },
2166
+ });
1614
2167
  const telegramBusLeaderRuntime =
1615
2168
  BusLeader.createTelegramBusLeaderRuntimeAssembly<Pi.ExtensionContext>({
1616
2169
  runtime: {
@@ -1621,6 +2174,7 @@ export default function (pi: Pi.ExtensionAPI) {
1621
2174
  followerRegistry: telegramBusFollowerRegistry,
1622
2175
  authSecret: telegramBusAuthSecret,
1623
2176
  protocolIdentity: telegramBusProtocolIdentity,
2177
+ selectedMenuDelivery,
1624
2178
  startPolling: pollingAdmissionRuntime.start,
1625
2179
  stopPolling: pollingAdmissionRuntime.stop,
1626
2180
  authorizeFollowerApiCall,
@@ -1634,15 +2188,21 @@ export default function (pi: Pi.ExtensionAPI) {
1634
2188
  message,
1635
2189
  });
1636
2190
  },
1637
- isFollowerProcessAlive: Locks.isProcessAlive,
1638
- afterFollowerPrune() { telegramSessionFolderSweeper.sweep(); },
2191
+ isFollowerProcessAlive: ProcessIdentity.isProcessAlive,
2192
+ afterFollowerPrune() {
2193
+ telegramSessionFolderSweeper.sweep();
2194
+ },
1639
2195
  shouldCleanupConfirmedDeadFollower:
1640
2196
  configControls.resolveAutomaticThreadCleanupEnabled,
1641
2197
  recordFollowerMessageOwnership(record) {
1642
2198
  messageOwnershipRuntime.recordFollower(record);
1643
2199
  },
1644
2200
  onWorkspaceRestoreRecipientObserved(follower, isCurrent) {
1645
- return inboundRouteRuntime.onWorkspaceRestoreRecipientObserved(follower, isCurrent, telegramSessionContextStore.get());
2201
+ return inboundRouteRuntime.onWorkspaceRestoreRecipientObserved(
2202
+ follower,
2203
+ isCurrent,
2204
+ telegramSessionContextStore.get(),
2205
+ );
1646
2206
  },
1647
2207
  },
1648
2208
  getAllowedUserId: configStore.getAllowedUserId,
@@ -1654,7 +2214,11 @@ export default function (pi: Pi.ExtensionAPI) {
1654
2214
  return Config.resolveTelegramThreadDisplayMode(configStore.get());
1655
2215
  },
1656
2216
  persistThreadDisplayMode(mode, isCurrent) {
1657
- return Config.setTelegramThreadDisplayMode(configStore, mode, isCurrent);
2217
+ return Config.setTelegramThreadDisplayMode(
2218
+ configStore,
2219
+ mode,
2220
+ isCurrent,
2221
+ );
1658
2222
  },
1659
2223
  onThreadDisplayChanged() {
1660
2224
  const ctx = telegramSessionContextStore.get();
@@ -1674,11 +2238,19 @@ export default function (pi: Pi.ExtensionAPI) {
1674
2238
  deleteThread: directTelegramApiRuntime.deleteWorkspaceThread,
1675
2239
  pruneJournalEvidence: pruneWorkspaceJournalEvidence,
1676
2240
  reclaimDeadOwnerQueuedWork(binding, isCurrent) {
1677
- return telegramWorkspaceOperationRuntime.run({
1678
- operationId: WorkspaceAdmission.createTelegramWorkspaceAdmissionOperationId(),
1679
- operationKind: "workspace.reclaim-dead-owner-queue",
1680
- scopes: [{ kind: "target", target: binding.target }],
1681
- }, reclaimTelegramWorkspaceDeadOwnerQueue.bind(undefined, binding, isCurrent));
2241
+ return telegramWorkspaceOperationRuntime.run(
2242
+ {
2243
+ operationId:
2244
+ WorkspaceAdmission.createTelegramWorkspaceAdmissionOperationId(),
2245
+ operationKind: "workspace.reclaim-dead-owner-queue",
2246
+ scopes: [{ kind: "target", target: binding.target }],
2247
+ },
2248
+ reclaimTelegramWorkspaceDeadOwnerQueue.bind(
2249
+ undefined,
2250
+ binding,
2251
+ isCurrent,
2252
+ ),
2253
+ );
1682
2254
  },
1683
2255
  },
1684
2256
  callApi(method, body, options) {
@@ -1776,10 +2348,19 @@ export default function (pi: Pi.ExtensionAPI) {
1776
2348
  const lockedPollingRuntime = Locks.createTelegramLockedPollingRuntime({
1777
2349
  lock: lockRuntime,
1778
2350
  resetDamagedState() {
1779
- return Locks.resetDamagedTelegramRuntimeState(Paths.resolveTelegramStatePath(), function (profile, sections) {
1780
- Threads.parseTelegramWorkspaceStateSection(sections.workspace, profile);
1781
- WorkspaceAdmission.assertTelegramConsolidatedAdmissionSection(sections.admission, profile);
1782
- });
2351
+ return Locks.resetDamagedTelegramRuntimeState(
2352
+ Paths.resolveTelegramStatePath(),
2353
+ function (profile, sections) {
2354
+ Threads.parseTelegramWorkspaceStateSection(
2355
+ sections.workspace,
2356
+ profile,
2357
+ );
2358
+ WorkspaceAdmission.assertTelegramConsolidatedAdmissionSection(
2359
+ sections.admission,
2360
+ profile,
2361
+ );
2362
+ },
2363
+ );
1783
2364
  },
1784
2365
  transportMonitor: telegramThreadCapabilityMonitor,
1785
2366
  hasBotToken: configStore.hasBotToken,
@@ -1791,8 +2372,7 @@ export default function (pi: Pi.ExtensionAPI) {
1791
2372
  stopPolling: threadAwarePollingPorts.stopPolling,
1792
2373
  registerFollowerWithOwner:
1793
2374
  threadAwarePollingPorts.registerFollowerWithOwner,
1794
- restoreFollowerWithOwner:
1795
- threadAwarePollingPorts.restoreFollowerWithOwner,
2375
+ restoreFollowerWithOwner: threadAwarePollingPorts.restoreFollowerWithOwner,
1796
2376
  stopFollowerRegistration: threadAwarePollingPorts.stopFollowerRegistration,
1797
2377
  onTransportAvailabilityChanged() {
1798
2378
  modelContextAvailabilityRuntime.reconcile();
@@ -1823,15 +2403,17 @@ export default function (pi: Pi.ExtensionAPI) {
1823
2403
  recordRuntimeEvent,
1824
2404
  runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1825
2405
  });
1826
- const prepareThreadPreservationOnQuit = Sync.createTelegramPreservedLeaderQuitHandler({
1827
- instanceId: telegramInstanceId,
1828
- topicTargetStore: threadStore,
1829
- getCurrentLeaderEpoch,
1830
- getProfileName: configStore.getActiveProfileName,
1831
- isPollingSuspended: lockedPollingRuntime.isSuspended,
1832
- resolveAutomaticThreadCleanupEnabled: configControls.resolveAutomaticThreadCleanupEnabled,
1833
- runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1834
- });
2406
+ const prepareThreadPreservationOnQuit =
2407
+ Sync.createTelegramPreservedLeaderQuitHandler({
2408
+ instanceId: telegramInstanceId,
2409
+ topicTargetStore: threadStore,
2410
+ getCurrentLeaderEpoch,
2411
+ getProfileName: configStore.getActiveProfileName,
2412
+ isPollingSuspended: lockedPollingRuntime.isSuspended,
2413
+ resolveAutomaticThreadCleanupEnabled:
2414
+ configControls.resolveAutomaticThreadCleanupEnabled,
2415
+ runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
2416
+ });
1835
2417
  const connectionIntent = Lifecycle.createTelegramConnectionIntentRuntime();
1836
2418
  const connectionLifecycle = Lifecycle.createTelegramConnectionLifecycle({
1837
2419
  intent: connectionIntent,
@@ -1839,12 +2421,17 @@ export default function (pi: Pi.ExtensionAPI) {
1839
2421
  isCurrent: telegramSessionContextStore.isCurrent,
1840
2422
  getProfileName: configStore.getActiveProfileName,
1841
2423
  isConnected() {
1842
- return lockRuntime.owns() || telegramBusFollowerRegistrationState.isRegistered();
2424
+ return (
2425
+ lockRuntime.owns() ||
2426
+ telegramBusFollowerRegistrationState.isRegistered()
2427
+ );
1843
2428
  },
1844
2429
  async activateProfile(profileName, isCurrent) {
1845
2430
  await configStore.load();
1846
2431
  if (!isCurrent()) return false;
1847
- return configStore.activateProfile(profileName) && configStore.hasBotToken();
2432
+ return (
2433
+ configStore.activateProfile(profileName) && configStore.hasBotToken()
2434
+ );
1848
2435
  },
1849
2436
  start(ctx) {
1850
2437
  return lockedPollingRuntime.start(ctx);
@@ -1866,7 +2453,9 @@ export default function (pi: Pi.ExtensionAPI) {
1866
2453
  syncFlags: lifecycle.syncFlags,
1867
2454
  bindDeferredDispatchContext: deferredQueueDispatchRuntime.bind,
1868
2455
  prepareTempDir() {
1869
- Recovery.removeTelegramLegacyRecoveryStorage(Paths.resolveTelegramTempDir());
2456
+ Recovery.removeTelegramLegacyRecoveryStorage(
2457
+ Paths.resolveTelegramTempDir(),
2458
+ );
1870
2459
  return prepareTempDir();
1871
2460
  },
1872
2461
  updateStatus,
@@ -1921,101 +2510,203 @@ export default function (pi: Pi.ExtensionAPI) {
1921
2510
 
1922
2511
  // --- Extension API Bindings ---
1923
2512
 
1924
- telegramThreadDisplayNameRenameBinding.bind({
1925
- async rename(
1926
- expectedTarget: Parameters<Commands.TelegramThreadDisplayNameRenamePort>[0],
1927
- threadName: string,
1928
- ) {
1929
- try {
1930
- if (telegramBusFollowerRegistrationState.isRegistered()) {
1931
- if (typeof expectedTarget.threadId !== "number") {
1932
- return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1933
- }
1934
- const renamedThreadName =
1935
- await telegramBusFollowerRegistration.renameThread?.(
1936
- { chatId: expectedTarget.chatId, threadId: expectedTarget.threadId },
1937
- threadName,
1938
- );
1939
- if (!renamedThreadName) {
1940
- return {
1941
- ok: false,
1942
- message: "Telegram follower Workspace Thread rename is unavailable.",
1943
- };
1944
- }
1945
- return {
1946
- ok: true,
1947
- threadName: renamedThreadName,
1948
- };
1949
- }
1950
- if (!ownsTelegramDirectDelivery()) {
1951
- return {
1952
- ok: false,
1953
- message: "Telegram Workspace Thread rename requires an active leader or follower connection.",
1954
- };
1955
- }
1956
- if (typeof expectedTarget.threadId !== "number") {
1957
- return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1958
- }
1959
- const renamed = await telegramBusLeaderRuntime.renameLeaderThreadAdmitted(
1960
- threadName,
1961
- { chatId: expectedTarget.chatId, threadId: expectedTarget.threadId },
1962
- );
1963
- telegramBusLeaderState.set({
1964
- target: renamed.target,
1965
- slot: renamed.slot,
1966
- threadName: renamed.manualThreadName ?? renamed.threadName,
1967
- });
2513
+ const threadNameRecipientAuthority = {
2514
+ getAuthority(): Threads.TelegramWorkspaceThreadRenameAuthority {
2515
+ const ctx = telegramSessionContextStore.get();
1968
2516
  return {
1969
- ok: true,
1970
- threadName: renamed.manualThreadName,
2517
+ context: ctx,
2518
+ sessionId: Pi.getExtensionContextSessionId(ctx),
2519
+ sessionGeneration: telegramSessionContextStore.getGeneration(),
2520
+ cwd: ctx && Pi.getExtensionContextCwd(ctx),
2521
+ profileName: configStore.getActiveProfileName(),
2522
+ botToken: configStore.getBotToken(),
2523
+ operatorUserId: configStore.getAllowedUserId(),
2524
+ leaderEpoch: getCurrentLeaderEpoch(),
2525
+ ownsDirectDelivery: ownsTelegramDirectDelivery(),
2526
+ followerRegistered: telegramBusFollowerRegistrationState.isRegistered(),
2527
+ localTarget: telegramBusLeaderState.getTarget(),
2528
+ localSlot: telegramBusLeaderState.getIdentity()?.slot,
1971
2529
  };
1972
- } catch (error) {
1973
- return {
1974
- ok: false,
1975
- message:
1976
- error instanceof Error
1977
- ? error.message
1978
- : "Telegram Workspace Thread rename failed.",
1979
- };
1980
- }
1981
2530
  },
1982
- }.rename);
1983
- telegramThreadDisplayNameResetBinding.bind({
1984
- async reset(
1985
- expectedTarget: Parameters<Commands.TelegramThreadDisplayNameResetPort>[0],
1986
- ) {
1987
- try {
1988
- if (typeof expectedTarget.threadId !== "number") {
1989
- return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1990
- }
1991
- const target = {
1992
- chatId: expectedTarget.chatId,
1993
- threadId: expectedTarget.threadId,
1994
- };
1995
- const wasFollower = telegramBusFollowerRegistrationState.isRegistered();
1996
- const result = wasFollower
1997
- ? await telegramBusFollowerRegistration.resetThreadName?.(target)
1998
- : (await telegramBusLeaderRuntime.resetLeaderThreadName(target)).threadName;
1999
- if (!result) {
2000
- return { ok: false, message: "Thread display name reset is unavailable." };
2531
+ };
2532
+ telegramThreadDisplayNameRenameBinding.bind(
2533
+ {
2534
+ async rename(
2535
+ expectedTarget: Parameters<Commands.TelegramThreadDisplayNameRenamePort>[0],
2536
+ threadName: string,
2537
+ options?: Parameters<Commands.TelegramThreadDisplayNameRenamePort>[2],
2538
+ ) {
2539
+ try {
2540
+ const assertCaller = options?.assertAuthority;
2541
+ assertCaller?.();
2542
+ if (telegramBusFollowerRegistrationState.isRegistered()) {
2543
+ if (assertCaller)
2544
+ return {
2545
+ ok: false,
2546
+ message:
2547
+ "Guarded follower Workspace Thread rename is unavailable.",
2548
+ };
2549
+ if (typeof expectedTarget.threadId !== "number") {
2550
+ return {
2551
+ ok: false,
2552
+ message: "Telegram Workspace Thread target is unavailable.",
2553
+ };
2554
+ }
2555
+ const renamedThreadName =
2556
+ await telegramBusFollowerRegistration.renameThread?.(
2557
+ {
2558
+ chatId: expectedTarget.chatId,
2559
+ threadId: expectedTarget.threadId,
2560
+ },
2561
+ threadName,
2562
+ );
2563
+ if (!renamedThreadName) {
2564
+ return {
2565
+ ok: false,
2566
+ message:
2567
+ "Telegram follower Workspace Thread rename is unavailable.",
2568
+ };
2569
+ }
2570
+ return {
2571
+ ok: true,
2572
+ threadName: renamedThreadName,
2573
+ };
2574
+ }
2575
+ if (!ownsTelegramDirectDelivery()) {
2576
+ return {
2577
+ ok: false,
2578
+ message:
2579
+ "Telegram Workspace Thread rename requires an active leader or follower connection.",
2580
+ };
2581
+ }
2582
+ if (typeof expectedTarget.threadId !== "number") {
2583
+ return {
2584
+ ok: false,
2585
+ message: "Telegram Workspace Thread target is unavailable.",
2586
+ };
2587
+ }
2588
+ const target = {
2589
+ chatId: expectedTarget.chatId,
2590
+ threadId: expectedTarget.threadId,
2591
+ };
2592
+ const recipient = assertCaller
2593
+ ? Threads.createTelegramWorkspaceThreadRenameRecipient({
2594
+ store: threadStore,
2595
+ instanceId: telegramInstanceId,
2596
+ target,
2597
+ assertAuthority: assertCaller,
2598
+ getAuthority: threadNameRecipientAuthority.getAuthority,
2599
+ })
2600
+ : undefined;
2601
+ const renamed =
2602
+ await telegramBusLeaderRuntime.renameLeaderThreadAdmitted(
2603
+ threadName,
2604
+ target,
2605
+ recipient?.assertAuthority,
2606
+ );
2607
+ recipient?.assertResult(renamed);
2608
+ telegramBusLeaderState.set({
2609
+ target: renamed.target,
2610
+ slot: renamed.slot,
2611
+ threadName: renamed.manualThreadName ?? renamed.threadName,
2612
+ });
2613
+ recipient?.assertResult(renamed);
2614
+ return {
2615
+ ok: true,
2616
+ threadName: renamed.manualThreadName,
2617
+ };
2618
+ } catch (error) {
2619
+ return {
2620
+ ok: false,
2621
+ message:
2622
+ error instanceof Error
2623
+ ? error.message
2624
+ : "Telegram Workspace Thread rename failed.",
2625
+ };
2001
2626
  }
2002
- const leaderTarget = telegramBusLeaderState.getTarget();
2003
- if (!wasFollower && leaderTarget) {
2004
- telegramBusLeaderState.set({ target: leaderTarget, threadName: result });
2627
+ },
2628
+ }.rename,
2629
+ );
2630
+ telegramThreadDisplayNameResetBinding.bind(
2631
+ {
2632
+ async reset(
2633
+ expectedTarget: Parameters<Commands.TelegramThreadDisplayNameResetPort>[0],
2634
+ options?: Parameters<Commands.TelegramThreadDisplayNameResetPort>[1],
2635
+ ) {
2636
+ try {
2637
+ const assertCaller = options?.assertAuthority;
2638
+ assertCaller?.();
2639
+ const wasFollower =
2640
+ telegramBusFollowerRegistrationState.isRegistered();
2641
+ if (wasFollower && assertCaller)
2642
+ return {
2643
+ ok: false,
2644
+ message:
2645
+ "Guarded follower Workspace Thread reset is unavailable.",
2646
+ };
2647
+ if (typeof expectedTarget.threadId !== "number") {
2648
+ return {
2649
+ ok: false,
2650
+ message: "Telegram Workspace Thread target is unavailable.",
2651
+ };
2652
+ }
2653
+ const target = {
2654
+ chatId: expectedTarget.chatId,
2655
+ threadId: expectedTarget.threadId,
2656
+ };
2657
+ const recipient = assertCaller
2658
+ ? Threads.createTelegramWorkspaceThreadResetRecipient({
2659
+ store: threadStore,
2660
+ instanceId: telegramInstanceId,
2661
+ target,
2662
+ assertAuthority: assertCaller,
2663
+ getAuthority: threadNameRecipientAuthority.getAuthority,
2664
+ })
2665
+ : undefined;
2666
+ const result = wasFollower
2667
+ ? await telegramBusFollowerRegistration.resetThreadName?.(target)
2668
+ : (
2669
+ await (recipient
2670
+ ? telegramBusLeaderRuntime.resetLeaderThreadName(
2671
+ target,
2672
+ recipient.assertAuthority,
2673
+ )
2674
+ : telegramBusLeaderRuntime.resetLeaderThreadName(target))
2675
+ ).threadName;
2676
+ if (!result) {
2677
+ return {
2678
+ ok: false,
2679
+ message: "Thread display name reset is unavailable.",
2680
+ };
2681
+ }
2682
+ recipient?.assertResult(result);
2683
+ const leaderTarget = telegramBusLeaderState.getTarget();
2684
+ if (!wasFollower && leaderTarget) {
2685
+ telegramBusLeaderState.set({
2686
+ target: leaderTarget,
2687
+ threadName: result,
2688
+ ...(recipient
2689
+ ? { slot: telegramBusLeaderState.getIdentity()?.slot }
2690
+ : {}),
2691
+ });
2692
+ }
2693
+ recipient?.assertResult(result);
2694
+ return {
2695
+ ok: true,
2696
+ threadName: result,
2697
+ };
2698
+ } catch (error) {
2699
+ return {
2700
+ ok: false,
2701
+ message:
2702
+ error instanceof Error
2703
+ ? error.message
2704
+ : "Thread display name reset failed.",
2705
+ };
2005
2706
  }
2006
- return {
2007
- ok: true,
2008
- threadName: result,
2009
- };
2010
- } catch (error) {
2011
- return {
2012
- ok: false,
2013
- message: error instanceof Error
2014
- ? error.message : "Thread display name reset failed.",
2015
- };
2016
- }
2017
- },
2018
- }.reset);
2707
+ },
2708
+ }.reset,
2709
+ );
2019
2710
  sessionActionsRuntime.register();
2020
2711
  Bindings.registerTelegramCommandsAndTools({
2021
2712
  pi,
@@ -2045,27 +2736,42 @@ export default function (pi: Pi.ExtensionAPI) {
2045
2736
  sendMarkdownReply,
2046
2737
  async sendChannelMarkdownMessage(channel, markdown, options) {
2047
2738
  if (!lockRuntime.owns()) {
2048
- throw new Error("Telegram channel delivery requires direct leader transport ownership.");
2739
+ throw new Error(
2740
+ "Telegram channel delivery requires direct leader transport ownership.",
2741
+ );
2049
2742
  }
2050
2743
  const profileName = configStore.getActiveProfileName() ?? "default";
2051
2744
  const botToken = configStore.getBotToken();
2052
- if (!botToken) throw new Error("Telegram channel delivery requires an active bot token.");
2053
- const store = ChannelPosts.createTelegramChannelPostJournalStore({
2054
- ...Paths.resolveTelegramServiceJournalStorage("channel-posts", undefined, profileName),
2745
+ if (!botToken)
2746
+ throw new Error(
2747
+ "Telegram channel delivery requires an active bot token.",
2748
+ );
2749
+ const store = ChannelPosts.openTelegramChannelPostJournalStore({
2055
2750
  profileName,
2056
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2751
+ botToken,
2057
2752
  });
2058
2753
  const record = await ChannelPosts.publishTelegramChannelPost({
2059
- store, operationId: options.operationId,
2060
- channel: channel as ChannelPosts.TelegramChannelPostAddress, markdown,
2754
+ store,
2755
+ operationId: options.operationId,
2756
+ channel: channel as ChannelPosts.TelegramChannelPostAddress,
2757
+ markdown,
2061
2758
  async observeChannel(channelAddress) {
2062
- return telegramApiRuntime.call("getChat", { chat_id: channelAddress });
2759
+ return telegramApiRuntime.call("getChat", {
2760
+ chat_id: channelAddress,
2761
+ });
2063
2762
  },
2064
2763
  async send(channelAddress, body) {
2065
- const sent = await telegramApiRuntime.call<TelegramApi.TelegramSentMessage & {
2066
- chat: { id: number; type: string };
2067
- }>("sendRichMessage", { chat_id: channelAddress, rich_message: { markdown: body },
2068
- ...(options.replyMarkup ? { reply_markup: options.replyMarkup } : {}) });
2764
+ const sent = await telegramApiRuntime.call<
2765
+ TelegramApi.TelegramSentMessage & {
2766
+ chat: { id: number; type: string };
2767
+ }
2768
+ >("sendRichMessage", {
2769
+ chat_id: channelAddress,
2770
+ rich_message: { markdown: body },
2771
+ ...(options.replyMarkup
2772
+ ? { reply_markup: options.replyMarkup }
2773
+ : {}),
2774
+ });
2069
2775
  return { messageId: sent.message_id, chat: sent.chat };
2070
2776
  },
2071
2777
  });
@@ -2073,34 +2779,54 @@ export default function (pi: Pi.ExtensionAPI) {
2073
2779
  },
2074
2780
  async sendChannelMediaMessage(channel, mediaPath, markdown, options) {
2075
2781
  if (!lockRuntime.owns()) {
2076
- throw new Error("Telegram channel media delivery requires direct leader transport ownership.");
2782
+ throw new Error(
2783
+ "Telegram channel media delivery requires direct leader transport ownership.",
2784
+ );
2077
2785
  }
2078
2786
  const profileName = configStore.getActiveProfileName() ?? "default";
2079
2787
  const botToken = configStore.getBotToken();
2080
- if (!botToken) throw new Error("Telegram channel media delivery requires an active bot token.");
2081
- const media = await ChannelPosts.inspectTelegramChannelPostMedia(mediaPath);
2788
+ if (!botToken)
2789
+ throw new Error(
2790
+ "Telegram channel media delivery requires an active bot token.",
2791
+ );
2792
+ const media =
2793
+ await ChannelPosts.inspectTelegramChannelPostMedia(mediaPath);
2082
2794
  const caption = Replies.renderTelegramMarkdownToHtmlDraft(markdown);
2083
2795
  ChannelPosts.assertTelegramChannelPostCaptionWithinLimit(caption);
2084
- const store = ChannelPosts.createTelegramChannelPostJournalStore({
2085
- ...Paths.resolveTelegramServiceJournalStorage("channel-posts", undefined, profileName),
2796
+ const store = ChannelPosts.openTelegramChannelPostJournalStore({
2086
2797
  profileName,
2087
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2798
+ botToken,
2088
2799
  });
2089
2800
  const record = await ChannelPosts.publishTelegramChannelPost({
2090
- store, operationId: options.operationId,
2091
- channel: channel as ChannelPosts.TelegramChannelPostAddress, markdown, media,
2801
+ store,
2802
+ operationId: options.operationId,
2803
+ channel: channel as ChannelPosts.TelegramChannelPostAddress,
2804
+ markdown,
2805
+ media,
2092
2806
  async observeChannel(channelAddress) {
2093
- return telegramApiRuntime.call("getChat", { chat_id: channelAddress });
2807
+ return telegramApiRuntime.call("getChat", {
2808
+ chat_id: channelAddress,
2809
+ });
2094
2810
  },
2095
2811
  async send(channelAddress) {
2096
- const sent = await telegramApiRuntime.callMultipart<TelegramApi.TelegramSentMessage & {
2097
- chat: { id: number; type: string };
2098
- }>(media.kind === "photo" ? "sendPhoto" : "sendVideo", {
2099
- chat_id: String(channelAddress),
2100
- caption,
2101
- parse_mode: "HTML",
2102
- ...(options.replyMarkup ? { reply_markup: JSON.stringify(options.replyMarkup) } : {}),
2103
- }, media.kind === "photo" ? "photo" : "video", mediaPath, media.fileName);
2812
+ const sent = await telegramApiRuntime.callMultipart<
2813
+ TelegramApi.TelegramSentMessage & {
2814
+ chat: { id: number; type: string };
2815
+ }
2816
+ >(
2817
+ media.kind === "photo" ? "sendPhoto" : "sendVideo",
2818
+ {
2819
+ chat_id: String(channelAddress),
2820
+ caption,
2821
+ parse_mode: "HTML",
2822
+ ...(options.replyMarkup
2823
+ ? { reply_markup: JSON.stringify(options.replyMarkup) }
2824
+ : {}),
2825
+ },
2826
+ media.kind === "photo" ? "photo" : "video",
2827
+ mediaPath,
2828
+ media.fileName,
2829
+ );
2104
2830
  return { messageId: sent.message_id, chat: sent.chat };
2105
2831
  },
2106
2832
  });
@@ -2109,61 +2835,108 @@ export default function (pi: Pi.ExtensionAPI) {
2109
2835
  listChannelPosts(input) {
2110
2836
  const profileName = configStore.getActiveProfileName() ?? "default";
2111
2837
  const botToken = configStore.getBotToken();
2112
- if (!botToken) throw new Error("Telegram channel posts require an active bot token.");
2113
- return ChannelPosts.createTelegramChannelPostJournalStore({
2114
- ...Paths.resolveTelegramServiceJournalStorage("channel-posts", undefined, profileName),
2838
+ if (!botToken)
2839
+ throw new Error("Telegram channel posts require an active bot token.");
2840
+ return ChannelPosts.openTelegramChannelPostJournalStore({
2115
2841
  profileName,
2116
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2842
+ botToken,
2117
2843
  }).list(input);
2118
2844
  },
2119
2845
  async mutateChannelPost(input) {
2120
- if (!lockRuntime.owns()) throw new Error("Telegram channel post mutation requires direct leader ownership.");
2846
+ if (!lockRuntime.owns())
2847
+ throw new Error(
2848
+ "Telegram channel post mutation requires direct leader ownership.",
2849
+ );
2121
2850
  const profileName = configStore.getActiveProfileName() ?? "default";
2122
2851
  const botToken = configStore.getBotToken();
2123
- if (!botToken) throw new Error("Telegram channel post mutation requires an active bot token.");
2124
- const store = ChannelPosts.createTelegramChannelPostJournalStore({
2125
- ...Paths.resolveTelegramServiceJournalStorage("channel-posts", undefined, profileName), profileName,
2126
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2852
+ if (!botToken)
2853
+ throw new Error(
2854
+ "Telegram channel post mutation requires an active bot token.",
2855
+ );
2856
+ const store = ChannelPosts.openTelegramChannelPostJournalStore({
2857
+ profileName,
2858
+ botToken,
2127
2859
  });
2128
2860
  if (input.action === "edit") {
2129
- if (!input.markdown) throw new Error("Telegram channel post edit requires markdown.");
2861
+ if (!input.markdown)
2862
+ throw new Error("Telegram channel post edit requires markdown.");
2130
2863
  const current = store.get(input.operationId);
2131
2864
  const caption = current?.media
2132
- ? Replies.renderTelegramMarkdownToHtmlDraft(input.markdown) : undefined;
2865
+ ? Replies.renderTelegramMarkdownToHtmlDraft(input.markdown)
2866
+ : undefined;
2133
2867
  if (caption !== undefined) {
2134
2868
  ChannelPosts.assertTelegramChannelPostCaptionWithinLimit(caption);
2135
2869
  }
2136
- const begun = store.beginEdit({ operationId: input.operationId,
2137
- mutationId: input.mutationId, markdown: input.markdown });
2870
+ const begun = store.beginEdit({
2871
+ operationId: input.operationId,
2872
+ mutationId: input.mutationId,
2873
+ markdown: input.markdown,
2874
+ });
2138
2875
  if (!begun.began) {
2139
- if (begun.record.state === "published" && begun.record.lastMutationId === input.mutationId)
2876
+ if (
2877
+ begun.record.state === "published" &&
2878
+ begun.record.lastMutationId === input.mutationId
2879
+ )
2140
2880
  return begun.record;
2141
- throw new Error("Telegram channel post edit outcome is unknown; refusing automatic replay.");
2881
+ throw new Error(
2882
+ "Telegram channel post edit outcome is unknown; refusing automatic replay.",
2883
+ );
2142
2884
  }
2143
- if (begun.record.state !== "edit-outcome-unknown") throw new Error("Telegram channel post edit authority is invalid.");
2885
+ if (begun.record.state !== "edit-outcome-unknown")
2886
+ throw new Error("Telegram channel post edit authority is invalid.");
2144
2887
  if (begun.record.media) {
2145
2888
  if (caption === undefined) {
2146
- throw new Error("Telegram channel post media caption edit requires retained media identity.");
2889
+ throw new Error(
2890
+ "Telegram channel post media caption edit requires retained media identity.",
2891
+ );
2147
2892
  }
2148
- await telegramApiRuntime.call("editMessageCaption", { chat_id: begun.record.channelId,
2149
- message_id: begun.record.messageId, caption, parse_mode: "HTML" });
2893
+ await telegramApiRuntime.call("editMessageCaption", {
2894
+ chat_id: begun.record.channelId,
2895
+ message_id: begun.record.messageId,
2896
+ caption,
2897
+ parse_mode: "HTML",
2898
+ });
2150
2899
  } else {
2151
- await telegramApiRuntime.call("editMessageText", { chat_id: begun.record.channelId,
2900
+ await telegramApiRuntime.call("editMessageText", {
2901
+ chat_id: begun.record.channelId,
2152
2902
  message_id: begun.record.messageId,
2153
- text: Replies.renderTelegramMarkdownToHtmlDraft(input.markdown), parse_mode: "HTML" });
2903
+ text: Replies.renderTelegramMarkdownToHtmlDraft(input.markdown),
2904
+ parse_mode: "HTML",
2905
+ });
2154
2906
  }
2155
- return store.confirmEdited({ operationId: input.operationId, mutationId: input.mutationId }).record;
2907
+ return store.confirmEdited({
2908
+ operationId: input.operationId,
2909
+ mutationId: input.mutationId,
2910
+ }).record;
2156
2911
  }
2157
- if (input.markdown !== undefined) throw new Error("Telegram channel post deletion does not accept markdown.");
2158
- const begun = store.beginDelete({ operationId: input.operationId, mutationId: input.mutationId });
2912
+ if (input.markdown !== undefined)
2913
+ throw new Error(
2914
+ "Telegram channel post deletion does not accept markdown.",
2915
+ );
2916
+ const begun = store.beginDelete({
2917
+ operationId: input.operationId,
2918
+ mutationId: input.mutationId,
2919
+ });
2159
2920
  if (!begun.began) {
2160
- if (begun.record.state === "deleted" && begun.record.mutationId === input.mutationId) return begun.record;
2161
- throw new Error("Telegram channel post deletion outcome is unknown; refusing automatic replay.");
2921
+ if (
2922
+ begun.record.state === "deleted" &&
2923
+ begun.record.mutationId === input.mutationId
2924
+ )
2925
+ return begun.record;
2926
+ throw new Error(
2927
+ "Telegram channel post deletion outcome is unknown; refusing automatic replay.",
2928
+ );
2162
2929
  }
2163
- if (begun.record.state !== "delete-outcome-unknown") throw new Error("Telegram channel post deletion authority is invalid.");
2164
- await telegramApiRuntime.call("deleteMessage", { chat_id: begun.record.channelId,
2165
- message_id: begun.record.messageId });
2166
- return store.confirmDeleted({ operationId: input.operationId, mutationId: input.mutationId }).record;
2930
+ if (begun.record.state !== "delete-outcome-unknown")
2931
+ throw new Error("Telegram channel post deletion authority is invalid.");
2932
+ await telegramApiRuntime.call("deleteMessage", {
2933
+ chat_id: begun.record.channelId,
2934
+ message_id: begun.record.messageId,
2935
+ });
2936
+ return store.confirmDeleted({
2937
+ operationId: input.operationId,
2938
+ mutationId: input.mutationId,
2939
+ }).record;
2167
2940
  },
2168
2941
  callMultipart,
2169
2942
  getDefaultChatId: proactivePushChatIdGetter,