@llblab/pi-kit 0.29.1 → 0.30.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 (222) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +12 -0
  5. package/node_modules/@llblab/pi-state-flow/README.md +2 -2
  6. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +2 -2
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +17 -8
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +7 -3
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +2 -1
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +3 -2
  11. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  12. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -3
  13. package/node_modules/@llblab/pi-state-flow/docs/usage.md +3 -2
  14. package/node_modules/@llblab/pi-state-flow/lib/context.ts +17 -8
  15. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +6 -3
  16. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +4 -2
  17. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  18. package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -10
  19. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -1
  20. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +23 -0
  21. package/node_modules/@llblab/pi-telegram/README.md +10 -11
  22. package/node_modules/@llblab/pi-telegram/api/commands.ts +3 -0
  23. package/node_modules/@llblab/pi-telegram/dist/api/commands.d.ts +1 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  25. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +15 -42
  26. package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +9 -0
  27. package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +39 -10
  28. package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.js +7 -3
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +43 -5
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +164 -20
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +12 -0
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +181 -77
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +111 -31
  34. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +1324 -359
  35. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +41 -91
  36. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +969 -637
  37. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +16 -9
  38. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +215 -18
  39. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +1048 -327
  40. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +5 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +434 -261
  42. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +26 -5
  43. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +92 -176
  44. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +168 -82
  45. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +1409 -291
  46. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +1 -8
  47. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +34 -76
  48. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -2
  49. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +16 -16
  50. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +642 -170
  51. package/node_modules/@llblab/pi-telegram/dist/lib/generative-app-worker.mjs +19 -7
  52. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -5
  53. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +61 -49
  54. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +5 -0
  55. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +35 -59
  56. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +40 -4
  57. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +2102 -760
  58. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -6
  59. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +75 -58
  60. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +34 -3
  61. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +318 -119
  62. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +50 -14
  63. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +22 -1
  64. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +46 -28
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +19 -5
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +53 -11
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +5 -4
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +45 -63
  69. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +5 -4
  70. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +114 -78
  71. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +1 -1
  72. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +8 -25
  73. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +6 -1
  74. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +14 -21
  75. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +8 -61
  76. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +92 -164
  77. package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +1 -1
  78. package/node_modules/@llblab/pi-telegram/dist/lib/model.js +2 -16
  79. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +6 -27
  80. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +45 -38
  81. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +22 -16
  82. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +0 -2
  83. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +51 -64
  84. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.js +6 -3
  85. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +6 -6
  86. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +61 -51
  87. package/node_modules/@llblab/pi-telegram/dist/lib/ownership.js +6 -2
  88. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +5 -0
  89. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +37 -21
  90. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -0
  91. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +8 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +23 -37
  93. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +71 -52
  94. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +39 -19
  95. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.d.ts +24 -0
  96. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.js +133 -0
  97. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +2 -7
  98. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +16 -16
  99. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +261 -116
  100. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +2 -83
  101. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +20 -274
  102. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +16 -24
  103. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +99 -116
  104. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +121 -7
  105. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +4750 -1161
  106. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -11
  107. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +4 -29
  108. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +7 -20
  109. package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +4 -3
  110. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +86 -57
  111. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +6 -3
  112. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +112 -45
  113. package/node_modules/@llblab/pi-telegram/dist/lib/target.d.ts +4 -0
  114. package/node_modules/@llblab/pi-telegram/dist/lib/target.js +18 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +18 -11
  116. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +165 -49
  117. package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +4 -39
  118. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +6 -27
  119. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +369 -186
  120. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -2
  121. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +58 -38
  122. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +28 -8
  123. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +175 -66
  124. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +42 -16
  125. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +90 -50
  126. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +88 -24
  127. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +2276 -842
  128. package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.js +1 -2
  129. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -19
  130. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +178 -82
  131. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +2438 -645
  132. package/node_modules/@llblab/pi-telegram/dist/lib/voice.d.ts +7 -22
  133. package/node_modules/@llblab/pi-telegram/dist/lib/voice.js +7 -22
  134. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +1 -1
  135. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +3 -1
  136. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +425 -211
  137. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +8 -13
  138. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -1
  139. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +328 -140
  140. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +8 -4
  141. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  142. package/node_modules/@llblab/pi-telegram/docs/architecture.md +43 -35
  143. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  144. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +15 -3
  145. package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
  146. package/node_modules/@llblab/pi-telegram/docs/public-api.md +29 -1
  147. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +25 -8
  148. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +59 -82
  149. package/node_modules/@llblab/pi-telegram/lib/activity.ts +146 -102
  150. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +12 -5
  151. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +390 -82
  152. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +262 -99
  153. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +2440 -601
  154. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2209 -1290
  155. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +46 -12
  156. package/node_modules/@llblab/pi-telegram/lib/bus.ts +1673 -444
  157. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +848 -327
  158. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +152 -265
  159. package/node_modules/@llblab/pi-telegram/lib/commands.ts +2398 -555
  160. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -170
  161. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +41 -29
  162. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1209 -431
  163. package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +19 -7
  164. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +324 -211
  165. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +95 -97
  166. package/node_modules/@llblab/pi-telegram/lib/journal.ts +5329 -1712
  167. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +1 -3
  168. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +128 -78
  169. package/node_modules/@llblab/pi-telegram/lib/locks.ts +703 -212
  170. package/node_modules/@llblab/pi-telegram/lib/logging.ts +147 -58
  171. package/node_modules/@llblab/pi-telegram/lib/media.ts +82 -43
  172. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +85 -18
  173. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +88 -129
  174. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +260 -155
  175. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +20 -60
  176. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +41 -56
  177. package/node_modules/@llblab/pi-telegram/lib/menu.ts +112 -285
  178. package/node_modules/@llblab/pi-telegram/lib/model.ts +8 -22
  179. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +156 -125
  180. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +76 -49
  181. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +96 -94
  182. package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +33 -27
  183. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +125 -125
  184. package/node_modules/@llblab/pi-telegram/lib/ownership.ts +19 -10
  185. package/node_modules/@llblab/pi-telegram/lib/paths.ts +148 -49
  186. package/node_modules/@llblab/pi-telegram/lib/pi.ts +35 -15
  187. package/node_modules/@llblab/pi-telegram/lib/polling.ts +165 -149
  188. package/node_modules/@llblab/pi-telegram/lib/preview.ts +93 -40
  189. package/node_modules/@llblab/pi-telegram/lib/process-identity.ts +164 -0
  190. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +4 -11
  191. package/node_modules/@llblab/pi-telegram/lib/queue.ts +523 -289
  192. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +50 -454
  193. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1 -3
  194. package/node_modules/@llblab/pi-telegram/lib/replies.ts +248 -209
  195. package/node_modules/@llblab/pi-telegram/lib/routing.ts +8527 -2149
  196. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +1 -12
  197. package/node_modules/@llblab/pi-telegram/lib/sections.ts +24 -89
  198. package/node_modules/@llblab/pi-telegram/lib/setup.ts +1 -3
  199. package/node_modules/@llblab/pi-telegram/lib/skills.ts +9 -4
  200. package/node_modules/@llblab/pi-telegram/lib/status.ts +215 -110
  201. package/node_modules/@llblab/pi-telegram/lib/sync.ts +488 -359
  202. package/node_modules/@llblab/pi-telegram/lib/target.ts +22 -3
  203. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +322 -122
  204. package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +5 -39
  205. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +833 -331
  206. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +213 -97
  207. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +232 -87
  208. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +155 -72
  209. package/node_modules/@llblab/pi-telegram/lib/threads.ts +5329 -1817
  210. package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +1 -4
  211. package/node_modules/@llblab/pi-telegram/lib/turns.ts +15 -28
  212. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5194 -1600
  213. package/node_modules/@llblab/pi-telegram/lib/voice.ts +7 -35
  214. package/node_modules/@llblab/pi-telegram/lib/wire.ts +1 -1
  215. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +996 -416
  216. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +14 -17
  217. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +878 -376
  218. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +22 -13
  219. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  220. package/package.json +3 -3
  221. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +0 -472
  222. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +0 -108
@@ -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,82 +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
- inspectTemporaryThreadSources(_target, requiredJournalBindingKeys) {
1344
+ inspectRoutingInputGroupExpiry(input) {
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
+ );
1357
+ },
1358
+ inspectTemporaryThreadSources(
1359
+ target,
1360
+ requiredJournalBindingKeys,
1361
+ ownInputs,
1362
+ ) {
1150
1363
  const binding = resolveTelegramUpdateJournalBinding();
1151
1364
  const botToken = configStore.getBotToken();
1152
1365
  if (!binding || !botToken) return undefined;
1153
1366
  const profile = configStore.getActiveProfileName() ?? "default";
1154
- const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({ botToken, botId: getTelegramBotId() });
1367
+ const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({
1368
+ botToken,
1369
+ botId: getTelegramBotId(),
1370
+ });
1155
1371
  return configStore.withSourceSerialization(function () {
1156
1372
  return Journal.isTelegramThreadCleanupJournalNamespaceClear({
1157
- directory: Paths.resolveTelegramTempDir(), profile, botIdentity,
1158
- pollingPath: telegramLeaderJournalPath.resolve(configStore.getActiveProfileName()),
1159
- requiredJournalBindingKeys: [binding.recoveryKey, ...requiredJournalBindingKeys],
1160
- limits: { maxDirectoryEntries: 10_000, maxFiles: 4096, maxBytes: 64 * 1024 * 1024,
1161
- 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
+ },
1162
1398
  withSourceReference(path, operation) {
1163
- return telegramJournalReferenceRegistry.withReference({ referenceClass: "operator-disposition",
1164
- 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
+ );
1165
1410
  },
1166
- }) ? [] : undefined;
1411
+ })
1412
+ ? []
1413
+ : undefined;
1167
1414
  });
1168
1415
  },
1169
1416
  hasWorkspaceRestoreAuthority() {
1170
- 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
+ );
1171
1441
  },
1172
1442
  getSessionGeneration: telegramSessionContextStore.getGeneration,
1173
1443
  bridgeRuntime,
1174
1444
  requestNewSession(source) {
1175
- const updateId = Updates.getTelegramUpdateExecutionFence(source)?.updateId;
1445
+ const updateId =
1446
+ Updates.getTelegramUpdateExecutionFence(source)?.updateId;
1176
1447
  if (updateId === undefined) {
1177
- throw new Error("Telegram session replacement requires durable update authority.");
1448
+ throw new Error(
1449
+ "Telegram session replacement requires durable update authority.",
1450
+ );
1178
1451
  }
1179
- const callbackMessage = source && typeof source === "object" && "message" in source
1180
- ? (source as { message?: unknown }).message
1181
- : source;
1182
- const messageTarget = callbackMessage as {
1183
- chat?: { id?: unknown };
1184
- message_id?: unknown;
1185
- message_thread_id?: unknown;
1186
- } | undefined;
1187
- const target = typeof messageTarget?.chat?.id === "number" &&
1188
- typeof messageTarget.message_id === "number"
1189
- ? {
1190
- chatId: messageTarget.chat.id,
1191
- messageId: messageTarget.message_id,
1192
- ...(typeof messageTarget.message_thread_id === "number"
1193
- ? { threadId: messageTarget.message_thread_id }
1194
- : {}),
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;
1195
1461
  }
1196
- : 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;
1197
1474
  if (!target) {
1198
1475
  throw new Error("Telegram session replacement target is unavailable.");
1199
1476
  }
@@ -1210,6 +1487,7 @@ export default function (pi: Pi.ExtensionAPI) {
1210
1487
  currentModelRuntime,
1211
1488
  modelSwitchController,
1212
1489
  menuActions,
1490
+ isVoiceReplyActive: menuActionPorts.isVoiceReplyActive,
1213
1491
  updateSettingsMenuMessage: settingsMenuRuntime.updateSettingsMenuMessage,
1214
1492
  openQueueMenu: queueMenuRuntime.openQueueMenu,
1215
1493
  queueMenuCallbackHandler: queueMenuRuntime.handleCallbackQuery,
@@ -1222,6 +1500,7 @@ export default function (pi: Pi.ExtensionAPI) {
1222
1500
  inboundHandlerRuntime,
1223
1501
  threadStore,
1224
1502
  runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1503
+ liveRebindCleanupSchedule: Routing.TELEGRAM_LIVE_REBIND_CLEANUP_SCHEDULE,
1225
1504
  updateStatus,
1226
1505
  isContextActive: telegramSessionContextStore.isCurrent,
1227
1506
  dispatchNextQueuedTelegramTurn,
@@ -1242,6 +1521,24 @@ export default function (pi: Pi.ExtensionAPI) {
1242
1521
  startGuestPlaceholder: guestPlaceholderRuntime.start,
1243
1522
  sendTextReply,
1244
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
+ },
1245
1542
  validateThreadName(threadName) {
1246
1543
  return ThreadNaming.getTelegramManualThreadDisplayNameValidationError(
1247
1544
  threadName,
@@ -1296,7 +1593,8 @@ export default function (pi: Pi.ExtensionAPI) {
1296
1593
  runtimeBinding: updateAdmissionRuntimeBinding,
1297
1594
  acquireSourceReference(role, binding) {
1298
1595
  return telegramJournalReferenceRegistry.acquire({
1299
- referenceClass: role === "leader" ? "leader-lifecycle" : "follower-lifecycle",
1596
+ referenceClass:
1597
+ role === "leader" ? "leader-lifecycle" : "follower-lifecycle",
1300
1598
  recoveryKey: binding.recoveryKey,
1301
1599
  });
1302
1600
  },
@@ -1317,28 +1615,46 @@ export default function (pi: Pi.ExtensionAPI) {
1317
1615
  },
1318
1616
  worker: {
1319
1617
  expireRoutingInput: inboundRouteRuntime.expireRoutingInput,
1320
- beforeQueueReceiptPublished: inboundRouteRuntime.beforeQueueReceiptPublished,
1618
+ beforeQueueReceiptPublished:
1619
+ inboundRouteRuntime.beforeQueueReceiptPublished,
1321
1620
  onQueueReceiptCompleted: inboundRouteRuntime.onQueueReceiptCompleted,
1322
1621
  defaultHandle: inboundRouteRuntime.handleUpdate,
1323
1622
  async shouldReviewHistoricalInput(entry, ctx, signal) {
1324
1623
  if (signal.aborted || !telegramSessionContextStore.isCurrent(ctx)) {
1325
1624
  throw new Error("Workspace Restore source generation ended.");
1326
1625
  }
1327
- const verdict = await inboundRouteRuntime.shouldReviewHistoricalInput(entry, ctx, signal);
1626
+ const verdict = await inboundRouteRuntime.shouldReviewHistoricalInput(
1627
+ entry,
1628
+ ctx,
1629
+ signal,
1630
+ );
1328
1631
  if (signal.aborted || !telegramSessionContextStore.isCurrent(ctx)) {
1329
1632
  throw new Error("Workspace Restore source generation ended.");
1330
1633
  }
1331
1634
  if (verdict === "retain") return verdict;
1332
1635
  const journalBindingKey = getTelegramQueueJournalBinding();
1333
- if (journalBindingKey && resolveWorkspaceRestoreStore()?.list().some(function (intent) {
1334
- return intent.request.source.journalBindingKey === journalBindingKey &&
1335
- intent.request.source.updateIds.includes(entry.updateId);
1336
- })) 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;
1337
1648
  return verdict;
1338
1649
  },
1339
1650
  spendHistoricalInput: true,
1340
1651
  onHeldSourcesPrepared(input): Promise<void> {
1341
- return inboundRouteRuntime.forgetPreviousWorld(input, lockedPollingRuntime.captureTransportAuthority).then(function () {});
1652
+ return inboundRouteRuntime
1653
+ .forgetPreviousWorld(
1654
+ input,
1655
+ lockedPollingRuntime.captureTransportAuthority,
1656
+ )
1657
+ .then(function () {});
1342
1658
  },
1343
1659
  async shouldHoldPendingInput(entry, ctx, signal) {
1344
1660
  if (signal.aborted || !telegramSessionContextStore.isCurrent(ctx)) {
@@ -1363,9 +1679,11 @@ export default function (pi: Pi.ExtensionAPI) {
1363
1679
  isRegistered: telegramBusFollowerRegistrationState.isRegistered,
1364
1680
  getGeneration: telegramBusFollowerRegistrationState.getGeneration,
1365
1681
  prepareBinding(binding) {
1366
- telegramJournalBindingRuntime.prepareActiveFollowerSuccession(function isCurrent() {
1367
- return binding.hasAuthority?.() === true;
1368
- });
1682
+ telegramJournalBindingRuntime.prepareActiveFollowerSuccession(
1683
+ function () {
1684
+ return binding.hasAuthority?.() === true;
1685
+ },
1686
+ );
1369
1687
  },
1370
1688
  prepareUpdateForExecution(update) {
1371
1689
  return BusFollower.prepareTelegramBusFollowerJournaledUpdateForExecution(
@@ -1446,32 +1764,159 @@ export default function (pi: Pi.ExtensionAPI) {
1446
1764
  local: agentMessageRuntime,
1447
1765
  follower: telegramBusFollowerClients.agentMessages,
1448
1766
  });
1449
- 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 = {
1450
1781
  instanceId: telegramInstanceId,
1451
- getContextAuthority: BusFollower.createTelegramBusFollowerRestoreContextGetter({
1452
- isContextCurrent: telegramSessionContextStore.isCurrent,
1453
- getSessionId: Pi.getExtensionContextSessionId,
1454
- getCwd: Pi.getExtensionContextCwd,
1455
- getGeneration: telegramSessionContextStore.getGeneration,
1456
- getProfileBindingKey() {
1457
- return workspaceAdmissionRuntime.resolve()?.getProfileKey();
1458
- },
1459
- getOperatorUserId: configStore.getAllowedUserId,
1460
- getLeaderState: lockRuntime.getState,
1461
- getAuthenticatedSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1462
- getLeaderProtocol: telegramBusFollowerRegistrationState.getLeaderProtocol,
1463
- }),
1464
- readRestoreIntent(operationId, profileBindingKey) {
1465
- if (profileBindingKey !== workspaceAdmissionRuntime.resolve()?.getProfileKey()) return undefined;
1466
- return resolveWorkspaceRestoreStore()?.list().find(function (intent) {
1467
- return intent.request.operationId === operationId;
1468
- });
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
+ });
1469
1805
  },
1470
1806
  topicTargetStore: threadStore,
1471
1807
  registrationState: telegramBusFollowerRegistrationState,
1472
1808
  getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
1473
1809
  recordRuntimeEvent,
1474
- });
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
+ });
1475
1920
  const telegramBusFollowerAssembly: BusFollower.TelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext> =
1476
1921
  BusFollower.createTelegramBusFollowerRuntimeAssembly<Pi.ExtensionContext>({
1477
1922
  instanceId: telegramInstanceId,
@@ -1482,12 +1927,63 @@ export default function (pi: Pi.ExtensionAPI) {
1482
1927
  getContext: telegramSessionContextStore.get,
1483
1928
  getAuthSecret: telegramBusFollowerControlState.getActiveAuthSecret,
1484
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,
1485
1973
  durableAdmission: followerDurableAdmissionRuntime,
1486
1974
  handleQueueHandoff: acceptStagedQueueHandoff,
1487
1975
  handleWorkspaceRestore: followerWorkspaceRestore,
1488
1976
  isWorkspaceRestoreEnabled() {
1489
- return Bus.hasTelegramBusCapability(telegramBusProtocolIdentity, Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
1490
- 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
+ );
1491
1987
  },
1492
1988
  },
1493
1989
  recovery: {
@@ -1530,7 +2026,9 @@ export default function (pi: Pi.ExtensionAPI) {
1530
2026
  >({
1531
2027
  state: pollingControllerState,
1532
2028
  canStart(ctx) {
1533
- return telegramSessionContextStore.isCurrent(ctx) && lockRuntime.owns(ctx);
2029
+ return (
2030
+ telegramSessionContextStore.isCurrent(ctx) && lockRuntime.owns(ctx)
2031
+ );
1534
2032
  },
1535
2033
  onPersistentConflict(ctx, count): Promise<void> {
1536
2034
  return lockedPollingRuntime.onPersistentConflict(ctx, count);
@@ -1553,7 +2051,9 @@ export default function (pi: Pi.ExtensionAPI) {
1553
2051
  registry: telegramJournalReferenceRegistry,
1554
2052
  resolveBinding: resolveTelegramUpdateJournalBinding,
1555
2053
  referenceClass: "polling-cursor",
1556
- operation(binding) { return binding.journal.read().acceptedThroughUpdateId; },
2054
+ operation(binding) {
2055
+ return binding.journal.read().acceptedThroughUpdateId;
2056
+ },
1557
2057
  });
1558
2058
  },
1559
2059
  async prepareCursorCutover() {
@@ -1561,30 +2061,37 @@ export default function (pi: Pi.ExtensionAPI) {
1561
2061
  registry: telegramJournalReferenceRegistry,
1562
2062
  resolveBinding: resolveTelegramUpdateJournalBinding,
1563
2063
  referenceClass: "polling-cursor",
1564
- operation(binding) { return Polling.cutOverTelegramPollingCursor({
1565
- getLegacyCursor: configStore.getLegacyPollingCursor,
1566
- readJournal: binding.journal.read,
1567
- publishJournalCursor(acceptedThroughUpdateId) {
1568
- binding.journal.appendBatch([], acceptedThroughUpdateId);
1569
- },
1570
- async removeLegacyCursor() {
1571
- configStore.removeLegacyPollingCursor();
1572
- await persistTelegramConfigWithSync();
1573
- },
1574
- }); },
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
+ },
1575
2077
  });
1576
- if (!cutover) throw new Error("Telegram update journal binding is unavailable.");
2078
+ if (!cutover)
2079
+ throw new Error("Telegram update journal binding is unavailable.");
1577
2080
  await cutover;
1578
2081
  },
1579
2082
  getEntryCount: updateAdmissionLifecycleRuntime.getJournalEntryCount,
1580
2083
  signalWorker: updateAdmissionLifecycleRuntime.signal,
1581
2084
  getBootstrapEntryCount() {
1582
- return Journal.withTelegramResolvedUpdateJournalReference({
1583
- registry: telegramJournalReferenceRegistry,
1584
- resolveBinding: resolveTelegramUpdateJournalBinding,
1585
- referenceClass: "polling-bootstrap",
1586
- operation(binding) { return binding.journal.read().entries.length; },
1587
- }) ?? 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
+ );
1588
2095
  },
1589
2096
  onSessionStart: updateAdmissionLifecycleRuntime.onSessionStart,
1590
2097
  },
@@ -1596,17 +2103,67 @@ export default function (pi: Pi.ExtensionAPI) {
1596
2103
  const authorizeFollowerApiCall = Bus.createTelegramFollowerApiCallAuthorizer({
1597
2104
  isMessageOwned: messageOwnershipRuntime.isOwnedByFollower,
1598
2105
  });
1599
- const telegramSessionFolderSweeper = Recovery.createTelegramSessionFolderSweeper({
1600
- getSessionsDir: Paths.resolveTelegramSessionsDir,
1601
- getProfileName: configStore.getActiveProfileName,
1602
- getKeptSessionIds() {
1603
- return [
1604
- ...threadStore.listWorkspaceBindings().map(function bindingSession(binding) { return binding.sessionId; }),
1605
- ...telegramBusFollowerRegistry.list().map(function followerSession(follower) { return follower.sessionId; }),
1606
- Pi.getExtensionContextSessionId(telegramSessionContextStore.get()),
1607
- ];
1608
- },
1609
- });
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
+ });
1610
2167
  const telegramBusLeaderRuntime =
1611
2168
  BusLeader.createTelegramBusLeaderRuntimeAssembly<Pi.ExtensionContext>({
1612
2169
  runtime: {
@@ -1617,6 +2174,7 @@ export default function (pi: Pi.ExtensionAPI) {
1617
2174
  followerRegistry: telegramBusFollowerRegistry,
1618
2175
  authSecret: telegramBusAuthSecret,
1619
2176
  protocolIdentity: telegramBusProtocolIdentity,
2177
+ selectedMenuDelivery,
1620
2178
  startPolling: pollingAdmissionRuntime.start,
1621
2179
  stopPolling: pollingAdmissionRuntime.stop,
1622
2180
  authorizeFollowerApiCall,
@@ -1630,15 +2188,21 @@ export default function (pi: Pi.ExtensionAPI) {
1630
2188
  message,
1631
2189
  });
1632
2190
  },
1633
- isFollowerProcessAlive: Locks.isProcessAlive,
1634
- afterFollowerPrune() { telegramSessionFolderSweeper.sweep(); },
2191
+ isFollowerProcessAlive: ProcessIdentity.isProcessAlive,
2192
+ afterFollowerPrune() {
2193
+ telegramSessionFolderSweeper.sweep();
2194
+ },
1635
2195
  shouldCleanupConfirmedDeadFollower:
1636
2196
  configControls.resolveAutomaticThreadCleanupEnabled,
1637
2197
  recordFollowerMessageOwnership(record) {
1638
2198
  messageOwnershipRuntime.recordFollower(record);
1639
2199
  },
1640
2200
  onWorkspaceRestoreRecipientObserved(follower, isCurrent) {
1641
- return inboundRouteRuntime.onWorkspaceRestoreRecipientObserved(follower, isCurrent, telegramSessionContextStore.get());
2201
+ return inboundRouteRuntime.onWorkspaceRestoreRecipientObserved(
2202
+ follower,
2203
+ isCurrent,
2204
+ telegramSessionContextStore.get(),
2205
+ );
1642
2206
  },
1643
2207
  },
1644
2208
  getAllowedUserId: configStore.getAllowedUserId,
@@ -1650,7 +2214,11 @@ export default function (pi: Pi.ExtensionAPI) {
1650
2214
  return Config.resolveTelegramThreadDisplayMode(configStore.get());
1651
2215
  },
1652
2216
  persistThreadDisplayMode(mode, isCurrent) {
1653
- return Config.setTelegramThreadDisplayMode(configStore, mode, isCurrent);
2217
+ return Config.setTelegramThreadDisplayMode(
2218
+ configStore,
2219
+ mode,
2220
+ isCurrent,
2221
+ );
1654
2222
  },
1655
2223
  onThreadDisplayChanged() {
1656
2224
  const ctx = telegramSessionContextStore.get();
@@ -1670,11 +2238,19 @@ export default function (pi: Pi.ExtensionAPI) {
1670
2238
  deleteThread: directTelegramApiRuntime.deleteWorkspaceThread,
1671
2239
  pruneJournalEvidence: pruneWorkspaceJournalEvidence,
1672
2240
  reclaimDeadOwnerQueuedWork(binding, isCurrent) {
1673
- return telegramWorkspaceOperationRuntime.run({
1674
- operationId: WorkspaceAdmission.createTelegramWorkspaceAdmissionOperationId(),
1675
- operationKind: "workspace.reclaim-dead-owner-queue",
1676
- scopes: [{ kind: "target", target: binding.target }],
1677
- }, 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
+ );
1678
2254
  },
1679
2255
  },
1680
2256
  callApi(method, body, options) {
@@ -1772,10 +2348,19 @@ export default function (pi: Pi.ExtensionAPI) {
1772
2348
  const lockedPollingRuntime = Locks.createTelegramLockedPollingRuntime({
1773
2349
  lock: lockRuntime,
1774
2350
  resetDamagedState() {
1775
- return Locks.resetDamagedTelegramRuntimeState(Paths.resolveTelegramStatePath(), function (profile, sections) {
1776
- Threads.parseTelegramWorkspaceStateSection(sections.workspace, profile);
1777
- WorkspaceAdmission.assertTelegramConsolidatedAdmissionSection(sections.admission, profile);
1778
- });
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
+ );
1779
2364
  },
1780
2365
  transportMonitor: telegramThreadCapabilityMonitor,
1781
2366
  hasBotToken: configStore.hasBotToken,
@@ -1787,8 +2372,7 @@ export default function (pi: Pi.ExtensionAPI) {
1787
2372
  stopPolling: threadAwarePollingPorts.stopPolling,
1788
2373
  registerFollowerWithOwner:
1789
2374
  threadAwarePollingPorts.registerFollowerWithOwner,
1790
- restoreFollowerWithOwner:
1791
- threadAwarePollingPorts.restoreFollowerWithOwner,
2375
+ restoreFollowerWithOwner: threadAwarePollingPorts.restoreFollowerWithOwner,
1792
2376
  stopFollowerRegistration: threadAwarePollingPorts.stopFollowerRegistration,
1793
2377
  onTransportAvailabilityChanged() {
1794
2378
  modelContextAvailabilityRuntime.reconcile();
@@ -1814,19 +2398,22 @@ export default function (pi: Pi.ExtensionAPI) {
1814
2398
  getSyncState: telegramSyncStateRuntime.getState,
1815
2399
  setSyncState: telegramSyncStateRuntime.setState,
1816
2400
  stopPolling: lockedPollingRuntime.stop,
2401
+ captureStopPolling: lockedPollingRuntime.captureStop,
1817
2402
  suspendPolling: lockedPollingRuntime.suspend,
1818
2403
  recordRuntimeEvent,
1819
2404
  runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1820
2405
  });
1821
- const prepareThreadPreservationOnQuit = Sync.createTelegramPreservedLeaderQuitHandler({
1822
- instanceId: telegramInstanceId,
1823
- topicTargetStore: threadStore,
1824
- getCurrentLeaderEpoch,
1825
- getProfileName: configStore.getActiveProfileName,
1826
- isPollingSuspended: lockedPollingRuntime.isSuspended,
1827
- resolveAutomaticThreadCleanupEnabled: configControls.resolveAutomaticThreadCleanupEnabled,
1828
- runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
1829
- });
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
+ });
1830
2417
  const connectionIntent = Lifecycle.createTelegramConnectionIntentRuntime();
1831
2418
  const connectionLifecycle = Lifecycle.createTelegramConnectionLifecycle({
1832
2419
  intent: connectionIntent,
@@ -1834,12 +2421,17 @@ export default function (pi: Pi.ExtensionAPI) {
1834
2421
  isCurrent: telegramSessionContextStore.isCurrent,
1835
2422
  getProfileName: configStore.getActiveProfileName,
1836
2423
  isConnected() {
1837
- return lockRuntime.owns() || telegramBusFollowerRegistrationState.isRegistered();
2424
+ return (
2425
+ lockRuntime.owns() ||
2426
+ telegramBusFollowerRegistrationState.isRegistered()
2427
+ );
1838
2428
  },
1839
2429
  async activateProfile(profileName, isCurrent) {
1840
2430
  await configStore.load();
1841
2431
  if (!isCurrent()) return false;
1842
- return configStore.activateProfile(profileName) && configStore.hasBotToken();
2432
+ return (
2433
+ configStore.activateProfile(profileName) && configStore.hasBotToken()
2434
+ );
1843
2435
  },
1844
2436
  start(ctx) {
1845
2437
  return lockedPollingRuntime.start(ctx);
@@ -1861,7 +2453,9 @@ export default function (pi: Pi.ExtensionAPI) {
1861
2453
  syncFlags: lifecycle.syncFlags,
1862
2454
  bindDeferredDispatchContext: deferredQueueDispatchRuntime.bind,
1863
2455
  prepareTempDir() {
1864
- Recovery.removeTelegramLegacyRecoveryStorage(Paths.resolveTelegramTempDir());
2456
+ Recovery.removeTelegramLegacyRecoveryStorage(
2457
+ Paths.resolveTelegramTempDir(),
2458
+ );
1865
2459
  return prepareTempDir();
1866
2460
  },
1867
2461
  updateStatus,
@@ -1916,101 +2510,203 @@ export default function (pi: Pi.ExtensionAPI) {
1916
2510
 
1917
2511
  // --- Extension API Bindings ---
1918
2512
 
1919
- telegramThreadDisplayNameRenameBinding.bind({
1920
- async rename(
1921
- expectedTarget: Parameters<Commands.TelegramThreadDisplayNameRenamePort>[0],
1922
- threadName: string,
1923
- ) {
1924
- try {
1925
- if (telegramBusFollowerRegistrationState.isRegistered()) {
1926
- if (typeof expectedTarget.threadId !== "number") {
1927
- return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1928
- }
1929
- const renamedThreadName =
1930
- await telegramBusFollowerRegistration.renameThread?.(
1931
- { chatId: expectedTarget.chatId, threadId: expectedTarget.threadId },
1932
- threadName,
1933
- );
1934
- if (!renamedThreadName) {
1935
- return {
1936
- ok: false,
1937
- message: "Telegram follower Workspace Thread rename is unavailable.",
1938
- };
1939
- }
1940
- return {
1941
- ok: true,
1942
- threadName: renamedThreadName,
1943
- };
1944
- }
1945
- if (!ownsTelegramDirectDelivery()) {
1946
- return {
1947
- ok: false,
1948
- message: "Telegram Workspace Thread rename requires an active leader or follower connection.",
1949
- };
1950
- }
1951
- if (typeof expectedTarget.threadId !== "number") {
1952
- return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1953
- }
1954
- const renamed = await telegramBusLeaderRuntime.renameLeaderThreadAdmitted(
1955
- threadName,
1956
- { chatId: expectedTarget.chatId, threadId: expectedTarget.threadId },
1957
- );
1958
- telegramBusLeaderState.set({
1959
- target: renamed.target,
1960
- slot: renamed.slot,
1961
- threadName: renamed.manualThreadName ?? renamed.threadName,
1962
- });
2513
+ const threadNameRecipientAuthority = {
2514
+ getAuthority(): Threads.TelegramWorkspaceThreadRenameAuthority {
2515
+ const ctx = telegramSessionContextStore.get();
1963
2516
  return {
1964
- ok: true,
1965
- 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,
1966
2529
  };
1967
- } catch (error) {
1968
- return {
1969
- ok: false,
1970
- message:
1971
- error instanceof Error
1972
- ? error.message
1973
- : "Telegram Workspace Thread rename failed.",
1974
- };
1975
- }
1976
2530
  },
1977
- }.rename);
1978
- telegramThreadDisplayNameResetBinding.bind({
1979
- async reset(
1980
- expectedTarget: Parameters<Commands.TelegramThreadDisplayNameResetPort>[0],
1981
- ) {
1982
- try {
1983
- if (typeof expectedTarget.threadId !== "number") {
1984
- return { ok: false, message: "Telegram Workspace Thread target is unavailable." };
1985
- }
1986
- const target = {
1987
- chatId: expectedTarget.chatId,
1988
- threadId: expectedTarget.threadId,
1989
- };
1990
- const wasFollower = telegramBusFollowerRegistrationState.isRegistered();
1991
- const result = wasFollower
1992
- ? await telegramBusFollowerRegistration.resetThreadName?.(target)
1993
- : (await telegramBusLeaderRuntime.resetLeaderThreadName(target)).threadName;
1994
- if (!result) {
1995
- 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
+ };
1996
2626
  }
1997
- const leaderTarget = telegramBusLeaderState.getTarget();
1998
- if (!wasFollower && leaderTarget) {
1999
- 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
+ };
2000
2706
  }
2001
- return {
2002
- ok: true,
2003
- threadName: result,
2004
- };
2005
- } catch (error) {
2006
- return {
2007
- ok: false,
2008
- message: error instanceof Error
2009
- ? error.message : "Thread display name reset failed.",
2010
- };
2011
- }
2012
- },
2013
- }.reset);
2707
+ },
2708
+ }.reset,
2709
+ );
2014
2710
  sessionActionsRuntime.register();
2015
2711
  Bindings.registerTelegramCommandsAndTools({
2016
2712
  pi,
@@ -2040,27 +2736,42 @@ export default function (pi: Pi.ExtensionAPI) {
2040
2736
  sendMarkdownReply,
2041
2737
  async sendChannelMarkdownMessage(channel, markdown, options) {
2042
2738
  if (!lockRuntime.owns()) {
2043
- 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
+ );
2044
2742
  }
2045
2743
  const profileName = configStore.getActiveProfileName() ?? "default";
2046
2744
  const botToken = configStore.getBotToken();
2047
- if (!botToken) throw new Error("Telegram channel delivery requires an active bot token.");
2048
- const store = ChannelPosts.createTelegramChannelPostJournalStore({
2049
- ...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({
2050
2750
  profileName,
2051
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2751
+ botToken,
2052
2752
  });
2053
2753
  const record = await ChannelPosts.publishTelegramChannelPost({
2054
- store, operationId: options.operationId,
2055
- channel: channel as ChannelPosts.TelegramChannelPostAddress, markdown,
2754
+ store,
2755
+ operationId: options.operationId,
2756
+ channel: channel as ChannelPosts.TelegramChannelPostAddress,
2757
+ markdown,
2056
2758
  async observeChannel(channelAddress) {
2057
- return telegramApiRuntime.call("getChat", { chat_id: channelAddress });
2759
+ return telegramApiRuntime.call("getChat", {
2760
+ chat_id: channelAddress,
2761
+ });
2058
2762
  },
2059
2763
  async send(channelAddress, body) {
2060
- const sent = await telegramApiRuntime.call<TelegramApi.TelegramSentMessage & {
2061
- chat: { id: number; type: string };
2062
- }>("sendRichMessage", { chat_id: channelAddress, rich_message: { markdown: body },
2063
- ...(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
+ });
2064
2775
  return { messageId: sent.message_id, chat: sent.chat };
2065
2776
  },
2066
2777
  });
@@ -2068,34 +2779,54 @@ export default function (pi: Pi.ExtensionAPI) {
2068
2779
  },
2069
2780
  async sendChannelMediaMessage(channel, mediaPath, markdown, options) {
2070
2781
  if (!lockRuntime.owns()) {
2071
- 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
+ );
2072
2785
  }
2073
2786
  const profileName = configStore.getActiveProfileName() ?? "default";
2074
2787
  const botToken = configStore.getBotToken();
2075
- if (!botToken) throw new Error("Telegram channel media delivery requires an active bot token.");
2076
- 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);
2077
2794
  const caption = Replies.renderTelegramMarkdownToHtmlDraft(markdown);
2078
2795
  ChannelPosts.assertTelegramChannelPostCaptionWithinLimit(caption);
2079
- const store = ChannelPosts.createTelegramChannelPostJournalStore({
2080
- ...Paths.resolveTelegramServiceJournalStorage("channel-posts", undefined, profileName),
2796
+ const store = ChannelPosts.openTelegramChannelPostJournalStore({
2081
2797
  profileName,
2082
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2798
+ botToken,
2083
2799
  });
2084
2800
  const record = await ChannelPosts.publishTelegramChannelPost({
2085
- store, operationId: options.operationId,
2086
- channel: channel as ChannelPosts.TelegramChannelPostAddress, markdown, media,
2801
+ store,
2802
+ operationId: options.operationId,
2803
+ channel: channel as ChannelPosts.TelegramChannelPostAddress,
2804
+ markdown,
2805
+ media,
2087
2806
  async observeChannel(channelAddress) {
2088
- return telegramApiRuntime.call("getChat", { chat_id: channelAddress });
2807
+ return telegramApiRuntime.call("getChat", {
2808
+ chat_id: channelAddress,
2809
+ });
2089
2810
  },
2090
2811
  async send(channelAddress) {
2091
- const sent = await telegramApiRuntime.callMultipart<TelegramApi.TelegramSentMessage & {
2092
- chat: { id: number; type: string };
2093
- }>(media.kind === "photo" ? "sendPhoto" : "sendVideo", {
2094
- chat_id: String(channelAddress),
2095
- caption,
2096
- parse_mode: "HTML",
2097
- ...(options.replyMarkup ? { reply_markup: JSON.stringify(options.replyMarkup) } : {}),
2098
- }, 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
+ );
2099
2830
  return { messageId: sent.message_id, chat: sent.chat };
2100
2831
  },
2101
2832
  });
@@ -2104,61 +2835,108 @@ export default function (pi: Pi.ExtensionAPI) {
2104
2835
  listChannelPosts(input) {
2105
2836
  const profileName = configStore.getActiveProfileName() ?? "default";
2106
2837
  const botToken = configStore.getBotToken();
2107
- if (!botToken) throw new Error("Telegram channel posts require an active bot token.");
2108
- return ChannelPosts.createTelegramChannelPostJournalStore({
2109
- ...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({
2110
2841
  profileName,
2111
- tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({ botToken }).tokenSha256,
2842
+ botToken,
2112
2843
  }).list(input);
2113
2844
  },
2114
2845
  async mutateChannelPost(input) {
2115
- 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
+ );
2116
2850
  const profileName = configStore.getActiveProfileName() ?? "default";
2117
2851
  const botToken = configStore.getBotToken();
2118
- if (!botToken) throw new Error("Telegram channel post mutation requires an active bot token.");
2119
- const store = ChannelPosts.createTelegramChannelPostJournalStore({
2120
- ...Paths.resolveTelegramServiceJournalStorage("channel-posts", undefined, profileName), profileName,
2121
- 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,
2122
2859
  });
2123
2860
  if (input.action === "edit") {
2124
- 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.");
2125
2863
  const current = store.get(input.operationId);
2126
2864
  const caption = current?.media
2127
- ? Replies.renderTelegramMarkdownToHtmlDraft(input.markdown) : undefined;
2865
+ ? Replies.renderTelegramMarkdownToHtmlDraft(input.markdown)
2866
+ : undefined;
2128
2867
  if (caption !== undefined) {
2129
2868
  ChannelPosts.assertTelegramChannelPostCaptionWithinLimit(caption);
2130
2869
  }
2131
- const begun = store.beginEdit({ operationId: input.operationId,
2132
- mutationId: input.mutationId, markdown: input.markdown });
2870
+ const begun = store.beginEdit({
2871
+ operationId: input.operationId,
2872
+ mutationId: input.mutationId,
2873
+ markdown: input.markdown,
2874
+ });
2133
2875
  if (!begun.began) {
2134
- if (begun.record.state === "published" && begun.record.lastMutationId === input.mutationId)
2876
+ if (
2877
+ begun.record.state === "published" &&
2878
+ begun.record.lastMutationId === input.mutationId
2879
+ )
2135
2880
  return begun.record;
2136
- 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
+ );
2137
2884
  }
2138
- 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.");
2139
2887
  if (begun.record.media) {
2140
2888
  if (caption === undefined) {
2141
- 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
+ );
2142
2892
  }
2143
- await telegramApiRuntime.call("editMessageCaption", { chat_id: begun.record.channelId,
2144
- 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
+ });
2145
2899
  } else {
2146
- await telegramApiRuntime.call("editMessageText", { chat_id: begun.record.channelId,
2900
+ await telegramApiRuntime.call("editMessageText", {
2901
+ chat_id: begun.record.channelId,
2147
2902
  message_id: begun.record.messageId,
2148
- text: Replies.renderTelegramMarkdownToHtmlDraft(input.markdown), parse_mode: "HTML" });
2903
+ text: Replies.renderTelegramMarkdownToHtmlDraft(input.markdown),
2904
+ parse_mode: "HTML",
2905
+ });
2149
2906
  }
2150
- return store.confirmEdited({ operationId: input.operationId, mutationId: input.mutationId }).record;
2907
+ return store.confirmEdited({
2908
+ operationId: input.operationId,
2909
+ mutationId: input.mutationId,
2910
+ }).record;
2151
2911
  }
2152
- if (input.markdown !== undefined) throw new Error("Telegram channel post deletion does not accept markdown.");
2153
- 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
+ });
2154
2920
  if (!begun.began) {
2155
- if (begun.record.state === "deleted" && begun.record.mutationId === input.mutationId) return begun.record;
2156
- 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
+ );
2157
2929
  }
2158
- if (begun.record.state !== "delete-outcome-unknown") throw new Error("Telegram channel post deletion authority is invalid.");
2159
- await telegramApiRuntime.call("deleteMessage", { chat_id: begun.record.channelId,
2160
- message_id: begun.record.messageId });
2161
- 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;
2162
2940
  },
2163
2941
  callMultipart,
2164
2942
  getDefaultChatId: proactivePushChatIdGetter,