@llblab/pi-telegram 0.52.2 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +7 -11
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +639 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +29 -3
  44. package/dist/lib/locks.js +289 -108
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +61 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +21 -37
  76. package/dist/lib/polling.js +59 -50
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +249 -126
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +1 -3
  95. package/dist/lib/sync.js +67 -43
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +148 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +31 -29
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1206 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +677 -203
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +125 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +144 -147
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +511 -300
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. package/scripts/measure-workspace.mjs +0 -108
@@ -5,17 +5,20 @@
5
5
  * heartbeat, forwarded-update receiving, and follower-routed API calls.
6
6
  * It must not spawn Pi processes or create hidden Telegram-originated instances.
7
7
  */
8
+ import { createHash } from "node:crypto";
8
9
  import { basename } from "node:path";
9
10
  import { isDeepStrictEqual } from "node:util";
11
+ import { getTelegramBusTransportRetryPolicy, TELEGRAM_BUS_REGISTRATION_RETRY, } from "./bus-transport.js";
12
+ import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES, TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES, TelegramBusLocalAuthorityError, } from "./bus.js";
13
+ import { parseTelegramUpdateJournalQueueOwner, } from "./journal.js";
14
+ import { TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
15
+ import { isPiStaleContextError } from "./pi.js";
16
+ import { parseTelegramTarget as parseTarget, } from "./target.js";
17
+ import { isTelegramApiMethodRetrySafe, TelegramApiAuthorityError, TelegramApiCommitUnknownError, TelegramApiStaleTargetError, } from "./telegram-api.js";
10
18
  import * as Threads from "./threads.js";
11
- import * as WorkspaceIdentity from "./workspace-identity.js";
12
19
  import { isWireRecord as isRecord } from "./wire.js";
13
- import { parseTelegramUpdateJournalQueueOwner } from "./journal.js";
14
- import { TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
15
- import { isTelegramApiMethodRetrySafe, TelegramApiCommitUnknownError, TelegramApiStaleTargetError, } from "./telegram-api.js";
16
- import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, } from "./bus.js";
17
- import { getTelegramBusTransportRetryPolicy, TELEGRAM_BUS_REGISTRATION_RETRY, } from "./bus-transport.js";
18
20
  import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
21
+ import * as WorkspaceIdentity from "./workspace-identity.js";
19
22
  const TELEGRAM_BUS_FOLLOWER_PROMOTION_GRACE_MS = 2_500;
20
23
  const TELEGRAM_FOLLOWER_SESSION_HANDOFF_TTL_MS = 30_000;
21
24
  const TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS = 30_000;
@@ -86,17 +89,16 @@ export function createTelegramBusFollowerPromotionHandler(input) {
86
89
  return (ctx, binding, election) => runTelegramBusFollowerWorkspaceMutation(input, "workspace.promote-follower", async () => {
87
90
  let promotedRecord;
88
91
  const promoted = await input.startLeader(ctx, election, async () => {
89
- promotedRecord =
90
- await Threads.promoteTelegramFollowerBindingToLeader({
91
- store: input.topicTargetStore,
92
- instanceId: input.instanceId,
93
- cwd: ctx.cwd,
94
- sessionId: input.getSessionId?.(ctx),
95
- telegramProfile: input.getActiveProfileName(),
96
- target: binding.target,
97
- slot: binding.slot,
98
- threadName: binding.threadName,
99
- });
92
+ promotedRecord = await Threads.promoteTelegramFollowerBindingToLeader({
93
+ store: input.topicTargetStore,
94
+ instanceId: input.instanceId,
95
+ cwd: ctx.cwd,
96
+ sessionId: input.getSessionId?.(ctx),
97
+ telegramProfile: input.getActiveProfileName(),
98
+ target: binding.target,
99
+ slot: binding.slot,
100
+ threadName: binding.threadName,
101
+ });
100
102
  if (!promotedRecord && typeof binding.target?.threadId === "number") {
101
103
  throw new Error("Telegram follower promotion slot authority is unavailable.");
102
104
  }
@@ -148,18 +150,23 @@ export function createTelegramBusFollowerRuntimeAssembly(ports) {
148
150
  const isReadyContext = (ctx) => Boolean(readyContext &&
149
151
  (ctx === undefined || readyContext.ctx === ctx) &&
150
152
  ports.registrationState.getGeneration() === readyContext.generation &&
151
- ports.registration.getSessionGeneration?.() === readyContext.sessionGeneration &&
152
- ports.registration.getSessionId?.(readyContext.ctx) === readyContext.sessionId &&
153
+ ports.registration.getSessionGeneration?.() ===
154
+ readyContext.sessionGeneration &&
155
+ ports.registration.getSessionId?.(readyContext.ctx) ===
156
+ readyContext.sessionId &&
153
157
  ports.registration.isContextActive?.(readyContext.ctx) !== false);
154
158
  const prepareContext = async (ctx) => {
155
159
  const generation = ports.registrationState.getGeneration();
156
160
  const sessionGeneration = ports.registration.getSessionGeneration?.();
157
161
  const sessionId = ports.registration.getSessionId?.(ctx);
158
162
  readyContext = undefined;
159
- if (!generation || (sessionId !== undefined && ports.registrationState.getSessionId(sessionId) !== sessionId))
163
+ if (!generation ||
164
+ (sessionId !== undefined &&
165
+ ports.registrationState.getSessionId(sessionId) !== sessionId))
160
166
  throw new Error("Telegram follower session change requires acknowledged leader registration.");
161
167
  await ports.registration.onRegistered?.(ctx);
162
- if (generation && ports.registrationState.getGeneration() === generation &&
168
+ if (generation &&
169
+ ports.registrationState.getGeneration() === generation &&
163
170
  ports.registration.getSessionGeneration?.() === sessionGeneration &&
164
171
  ports.registration.getSessionId?.(ctx) === sessionId &&
165
172
  ports.registration.isContextActive?.(ctx) !== false)
@@ -208,7 +215,11 @@ export function createTelegramBusFollowerRuntimeAssembly(ports) {
208
215
  await prepareContext(ctx);
209
216
  },
210
217
  };
211
- return { receiver, registration, getReadySessionId: () => isReadyContext() ? readyContext?.sessionId : undefined };
218
+ return {
219
+ receiver,
220
+ registration,
221
+ getReadySessionId: () => isReadyContext() ? readyContext?.sessionId : undefined,
222
+ };
212
223
  }
213
224
  /** Binds Restore to real Pi lifetime and the current authenticated transport owner, not request fields. */
214
225
  export function createTelegramBusFollowerRestoreContextGetter(deps) {
@@ -218,42 +229,105 @@ export function createTelegramBusFollowerRestoreContextGetter(deps) {
218
229
  const leader = deps.getLeaderState();
219
230
  const secret = deps.getAuthenticatedSecret();
220
231
  const leaderProtocol = deps.getLeaderProtocol();
221
- if (!leaderProtocol || !hasTelegramBusCapability(leaderProtocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE))
232
+ if (!leaderProtocol ||
233
+ !hasTelegramBusCapability(leaderProtocol, deps.capability ?? TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE))
222
234
  return undefined;
223
- if (leader.kind !== "active-elsewhere" || !secret || leader.lock.busSecret !== secret ||
224
- !leader.lock.instanceId || leader.lock.leaderEpoch === undefined)
235
+ if (leader.kind !== "active-elsewhere" ||
236
+ !secret ||
237
+ leader.lock.busSecret !== secret ||
238
+ !leader.lock.instanceId ||
239
+ leader.lock.leaderEpoch === undefined)
225
240
  return undefined;
226
241
  const profileBindingKey = deps.getProfileBindingKey();
227
242
  const operatorUserId = deps.getOperatorUserId();
228
243
  const sessionId = deps.getSessionId(ctx);
229
244
  const cwd = deps.getCwd(ctx);
230
245
  const generation = deps.getGeneration();
231
- if (!profileBindingKey || !sessionId || !cwd || typeof operatorUserId !== "number" ||
232
- !Number.isSafeInteger(operatorUserId) || operatorUserId <= 0 ||
233
- !Number.isSafeInteger(generation) || generation < 0 || !deps.isContextCurrent(ctx))
246
+ if (!profileBindingKey ||
247
+ !sessionId ||
248
+ !cwd ||
249
+ typeof operatorUserId !== "number" ||
250
+ !Number.isSafeInteger(operatorUserId) ||
251
+ operatorUserId <= 0 ||
252
+ !Number.isSafeInteger(generation) ||
253
+ generation < 0 ||
254
+ !deps.isContextCurrent(ctx))
234
255
  return undefined;
235
- return { executor: { instanceId: leader.lock.instanceId, leaderEpoch: String(leader.lock.leaderEpoch) },
236
- profileBindingKey, operatorUserId, sessionId, cwd, generation, leaderProtocol: structuredClone(leaderProtocol) };
256
+ return {
257
+ executor: {
258
+ instanceId: leader.lock.instanceId,
259
+ leaderEpoch: String(leader.lock.leaderEpoch),
260
+ },
261
+ profileBindingKey,
262
+ operatorUserId,
263
+ sessionId,
264
+ cwd,
265
+ generation,
266
+ leaderProtocol: structuredClone(leaderProtocol),
267
+ };
237
268
  };
238
269
  }
239
- /** Prepared receiver: its caller authenticates the envelope; no canonical writes, dispatch or cleanup. */
270
+ /** Shared canonical/local target owner and scoped live-carrier hooks; caller authenticates transport, no canonical writes or cleanup. */
240
271
  export function createTelegramBusFollowerWorkspaceRestoreHandler(deps) {
241
- return async (input, ctx) => {
272
+ const handler = async (input, ctx) => {
273
+ const scope = input.liveRebind, ports = scope && {
274
+ isCurrent: scope.isCurrent,
275
+ release: scope.release,
276
+ observeWork: scope.observeWork,
277
+ };
278
+ const suppliedTarget = scope?.expectedTarget, expectedTarget = suppliedTarget && { ...suppliedTarget };
279
+ const sourceUpdateIds = scope && [...scope.sourceUpdateIds].sort((a, b) => a - b);
280
+ const liveCurrent = scope && ports.isCurrent.bind(scope), release = scope && ports.release?.bind(scope), workObserver = scope && ports.observeWork?.bind(scope);
242
281
  const captured = deps.getContextAuthority(ctx);
243
282
  if (!captured)
244
283
  throw new Error("Stale Telegram follower Restore context.");
245
284
  const authority = structuredClone(captured);
246
- const request = structuredClone(input);
247
- if (!authority.profileBindingKey || !authority.sessionId || !authority.cwd ||
248
- !authority.executor?.instanceId || !authority.executor.leaderEpoch ||
249
- !(typeof authority.generation === "string" ? authority.generation.length > 0 :
250
- Number.isSafeInteger(authority.generation) && authority.generation >= 0) ||
251
- !Number.isSafeInteger(authority.operatorUserId) || authority.operatorUserId <= 0 ||
252
- !request.operationId || !request.registrationGeneration || !["apply", "inspect"].includes(request.mode))
285
+ const request = structuredClone({
286
+ operationId: input.operationId,
287
+ registrationGeneration: input.registrationGeneration,
288
+ mode: input.mode,
289
+ });
290
+ const readIntent = () => liveCurrent
291
+ ? deps.readLiveRebindIntent?.(request.operationId, authority.profileBindingKey)
292
+ : deps.readRestoreIntent(request.operationId, authority.profileBindingKey);
293
+ let sampleWork;
294
+ if (suppliedTarget !== undefined && !expectedTarget)
295
+ throw new Error("Invalid prepared live-rebind target.");
296
+ if (expectedTarget &&
297
+ (!Number.isSafeInteger(expectedTarget.chatId) ||
298
+ expectedTarget.chatId !== authority.operatorUserId ||
299
+ !Number.isSafeInteger(expectedTarget.threadId) ||
300
+ expectedTarget.threadId <= 0))
301
+ throw new Error("Invalid prepared live-rebind target.");
302
+ if ((release || workObserver) &&
303
+ !hasTelegramBusCapability(authority.leaderProtocol, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE))
304
+ throw new Error("Live-rebind settlement capability is unavailable.");
305
+ if (liveCurrent &&
306
+ !hasTelegramBusCapability(authority.leaderProtocol, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY))
307
+ throw new Error("Live-rebind apply capability is unavailable.");
308
+ if (!authority.profileBindingKey ||
309
+ !authority.sessionId ||
310
+ !authority.cwd ||
311
+ !authority.executor?.instanceId ||
312
+ !authority.executor.leaderEpoch ||
313
+ !(typeof authority.generation === "string"
314
+ ? authority.generation.length > 0
315
+ : Number.isSafeInteger(authority.generation) &&
316
+ authority.generation >= 0) ||
317
+ !Number.isSafeInteger(authority.operatorUserId) ||
318
+ authority.operatorUserId <= 0 ||
319
+ !request.operationId ||
320
+ !request.registrationGeneration ||
321
+ !["apply", "inspect"].includes(request.mode))
253
322
  throw new Error("Invalid Telegram follower Restore authority.");
254
323
  const assertCurrent = () => {
255
324
  if (!isDeepStrictEqual(deps.getContextAuthority(ctx), authority) ||
256
- deps.registrationState.getGeneration() !== request.registrationGeneration)
325
+ liveCurrent?.() === false ||
326
+ (scope &&
327
+ (input.liveRebind !== scope ||
328
+ Object.entries(ports).some(([key, value]) => Reflect.get(scope, key) !== value))) ||
329
+ deps.registrationState.getGeneration() !==
330
+ request.registrationGeneration)
257
331
  throw new Error("Stale Telegram follower Restore authority.");
258
332
  };
259
333
  assertCurrent();
@@ -261,35 +335,61 @@ export function createTelegramBusFollowerWorkspaceRestoreHandler(deps) {
261
335
  throw new Error("Telegram Workspace admission authority is unavailable.");
262
336
  const result = await runTelegramBusFollowerWorkspaceMutation(deps, "workspace.restore-follower-target", async () => {
263
337
  assertCurrent();
264
- const intent = deps.readRestoreIntent(request.operationId, authority.profileBindingKey);
338
+ const intent = readIntent();
265
339
  assertCurrent();
266
- if (!intent || intent.request.operationId !== request.operationId ||
267
- (intent.phase !== "recipient-issued" && intent.phase !== "ready") ||
340
+ if (!intent ||
341
+ intent.request.operationId !== request.operationId ||
342
+ (liveCurrent
343
+ ? !("kind" in intent) ||
344
+ intent.kind !== "live-rebind" ||
345
+ !isDeepStrictEqual([...intent.request.source.updateIds].sort((a, b) => a - b), sourceUpdateIds) ||
346
+ (request.mode === "apply"
347
+ ? intent.phase !== "rebound"
348
+ : !["rebound", "released", "finished"].includes(intent.phase))
349
+ : "kind" in intent ||
350
+ (intent.phase !== "recipient-issued" &&
351
+ intent.phase !== "ready")) ||
352
+ (expectedTarget &&
353
+ !isDeepStrictEqual(intent.request.target, expectedTarget)) ||
268
354
  !isDeepStrictEqual(intent.executor, authority.executor) ||
269
- intent.operatorUserId !== authority.operatorUserId || !intent.recipient ||
270
- (request.mode === "apply" && (intent.recipient.kind !== "follower" || intent.recipient.instanceId !== deps.instanceId ||
271
- intent.recipient.generation !== request.registrationGeneration)) ||
272
- intent.recipient.sessionId !== authority.sessionId || intent.request.binding.sessionId !== authority.sessionId ||
273
- intent.request.binding.cwd !== WorkspaceIdentity.normalizeTelegramWorkspacePath(authority.cwd))
355
+ intent.operatorUserId !== authority.operatorUserId ||
356
+ !intent.recipient ||
357
+ ((request.mode === "apply" || liveCurrent) &&
358
+ (intent.recipient.kind !== "follower" ||
359
+ intent.recipient.instanceId !== deps.instanceId ||
360
+ intent.recipient.generation !==
361
+ request.registrationGeneration)) ||
362
+ intent.recipient.sessionId !== authority.sessionId ||
363
+ intent.request.binding.sessionId !== authority.sessionId ||
364
+ intent.request.binding.cwd !==
365
+ WorkspaceIdentity.normalizeTelegramWorkspacePath(authority.cwd))
274
366
  throw new Error("Telegram follower Restore intent does not match this recipient.");
275
367
  const expected = structuredClone(intent);
276
368
  await deps.topicTargetStore.load();
277
369
  assertCurrent();
278
370
  const observe = (canonical) => {
279
- if (!isDeepStrictEqual(deps.readRestoreIntent(request.operationId, authority.profileBindingKey), expected))
371
+ if (!isDeepStrictEqual(readIntent(), expected))
280
372
  throw new Error("Telegram follower Restore intent changed.");
281
373
  assertCurrent();
282
374
  const { binding, target } = expected.request;
283
- const bindings = (canonical.workspaceBindings ?? []).filter(value => value.bindingKey === binding.bindingKey);
375
+ const bindings = (canonical.workspaceBindings ?? []).filter((value) => value.bindingKey === binding.bindingKey);
284
376
  const snapshot = canonical.threads;
285
- const records = snapshot.filter(value => value.instanceId === deps.instanceId);
286
- const owners = snapshot.filter(value => value.status === "active" &&
287
- (value.slot === binding.slot || isDeepStrictEqual(value.target, target)));
288
- if (bindings.length !== 1 || records.length !== 1 || owners.length !== 1 || bindings[0]?.cwd !== binding.cwd ||
289
- bindings[0]?.sessionId !== binding.sessionId || bindings[0]?.slot !== binding.slot || bindings[0]?.inactiveSinceMs !== undefined ||
290
- !isDeepStrictEqual(bindings[0]?.target, target) || records[0]?.status !== "active" ||
377
+ const records = snapshot.filter((value) => value.instanceId === deps.instanceId);
378
+ const owners = snapshot.filter((value) => value.status === "active" &&
379
+ (value.slot === binding.slot ||
380
+ isDeepStrictEqual(value.target, target)));
381
+ if (bindings.length !== 1 ||
382
+ records.length !== 1 ||
383
+ owners.length !== 1 ||
384
+ bindings[0]?.cwd !== binding.cwd ||
385
+ bindings[0]?.sessionId !== binding.sessionId ||
386
+ bindings[0]?.slot !== binding.slot ||
387
+ bindings[0]?.inactiveSinceMs !== undefined ||
388
+ !isDeepStrictEqual(bindings[0]?.target, target) ||
389
+ records[0]?.status !== "active" ||
291
390
  records[0]?.owner?.kind !== "manual-follower" ||
292
- records[0]?.slot !== binding.slot || !isDeepStrictEqual(records[0]?.target, target) ||
391
+ records[0]?.slot !== binding.slot ||
392
+ !isDeepStrictEqual(records[0]?.target, target) ||
293
393
  deps.registrationState.getSlot() !== binding.slot)
294
394
  throw new Error("Telegram follower Restore canonical binding is not committed.");
295
395
  const localTarget = deps.registrationState.getTarget();
@@ -298,33 +398,223 @@ export function createTelegramBusFollowerWorkspaceRestoreHandler(deps) {
298
398
  throw new Error("Telegram follower Restore local target changed.");
299
399
  assertCurrent();
300
400
  if (!alreadyReady && request.mode === "apply") {
301
- if (expected.phase !== "recipient-issued")
401
+ if (expected.phase !== (liveCurrent ? "rebound" : "recipient-issued"))
302
402
  throw new Error("Telegram follower Restore readiness regressed.");
303
- deps.registrationState.setRegistered(true, target, { slot: binding.slot,
304
- threadName: records[0]?.threadName, generation: request.registrationGeneration,
305
- leaderProtocol: deps.registrationState.getLeaderProtocol() });
403
+ deps.registrationState.setRegistered(true, target, {
404
+ slot: binding.slot,
405
+ threadName: records[0]?.threadName,
406
+ generation: request.registrationGeneration,
407
+ leaderProtocol: deps.registrationState.getLeaderProtocol(),
408
+ });
306
409
  }
307
410
  assertCurrent();
308
411
  const observedTarget = deps.registrationState.getTarget();
309
- if (!observedTarget || (!isDeepStrictEqual(observedTarget, target) && !isDeepStrictEqual(observedTarget, binding.target)))
412
+ if (!observedTarget ||
413
+ (!isDeepStrictEqual(observedTarget, target) &&
414
+ !isDeepStrictEqual(observedTarget, binding.target)))
310
415
  throw new Error("Telegram follower Restore local target changed.");
311
416
  const ready = isDeepStrictEqual(observedTarget, target);
312
417
  assertCurrent();
313
- return { operationId: request.operationId, recipient: { kind: "follower", instanceId: deps.instanceId,
314
- sessionId: authority.sessionId, generation: request.registrationGeneration },
315
- target: { chatId: observedTarget.chatId, threadId: observedTarget.threadId }, slot: binding.slot, ready };
418
+ return {
419
+ operationId: request.operationId,
420
+ recipient: {
421
+ kind: "follower",
422
+ instanceId: deps.instanceId,
423
+ sessionId: authority.sessionId,
424
+ generation: request.registrationGeneration,
425
+ },
426
+ target: {
427
+ chatId: observedTarget.chatId,
428
+ threadId: observedTarget.threadId,
429
+ },
430
+ slot: binding.slot,
431
+ ready,
432
+ };
316
433
  };
317
434
  let result;
318
- deps.topicTargetStore.withWorkspaceRestoreSnapshot(expected, snapshot => { result = observe(snapshot); });
435
+ deps.topicTargetStore.withWorkspaceRestoreSnapshot(expected, (snapshot) => {
436
+ result = observe(snapshot);
437
+ });
319
438
  if (!result)
320
439
  throw new Error("Telegram follower Restore observation was not completed.");
440
+ if (release || workObserver) {
441
+ if (!("kind" in expected) ||
442
+ expected.kind !== "live-rebind" ||
443
+ expected.phase !== "released" ||
444
+ !result.ready ||
445
+ request.mode !== "inspect")
446
+ throw new Error("Live-rebind dispatch has no canonical released/local-applied grant.");
447
+ // Journal reads stay outside the canonical transaction; each barrier check reobserves only Workspace authority.
448
+ if (workObserver)
449
+ sampleWork = () => {
450
+ deps.topicTargetStore.withWorkspaceRestoreSnapshot(expected, (snapshot) => {
451
+ if (!observe(snapshot).ready)
452
+ throw new Error("Live-rebind work observation lost local target application.");
453
+ workObserver(expected.request.binding.target);
454
+ assertCurrent();
455
+ });
456
+ };
457
+ release?.(() => {
458
+ assertCurrent();
459
+ let ready = false;
460
+ deps.topicTargetStore.withWorkspaceRestoreSnapshot(expected, (snapshot) => {
461
+ ready = observe(snapshot).ready;
462
+ });
463
+ assertCurrent();
464
+ return ready;
465
+ });
466
+ }
321
467
  return result;
322
468
  });
323
469
  assertCurrent();
324
- if (!isDeepStrictEqual(deps.registrationState.getTarget(), result.target) || deps.registrationState.getSlot() !== result.slot)
470
+ if (!isDeepStrictEqual(deps.registrationState.getTarget(), result.target) ||
471
+ deps.registrationState.getSlot() !== result.slot)
325
472
  throw new Error("Telegram follower Restore local registration changed after admission release.");
473
+ // Work can change while admission releases; sample only after that await under a fresh canonical snapshot.
474
+ sampleWork?.();
475
+ assertCurrent();
326
476
  return result;
327
477
  };
478
+ return Object.assign(handler, {
479
+ /** Capture availability only; asserting current recipient requires canonical released/local-applied ownership. */
480
+ prepareLiveCommandRecipient(input, ctx) {
481
+ const expected = structuredClone(input), getAuthority = deps.getContextAuthority, read = deps.readLiveRebindIntent, store = deps.topicTargetStore;
482
+ const snapshot = store.withWorkspaceLiveRebindSnapshot, state = deps.registrationState;
483
+ const statePorts = {
484
+ getTarget: state.getTarget,
485
+ getSlot: state.getSlot,
486
+ getGeneration: state.getGeneration,
487
+ getLeaderProtocol: state.getLeaderProtocol,
488
+ };
489
+ const instanceId = deps.instanceId, authority = structuredClone(getAuthority.call(deps, ctx)), oldTarget = structuredClone(state.getTarget()), slot = state.getSlot();
490
+ if (!authority ||
491
+ !read ||
492
+ !snapshot ||
493
+ !instanceId ||
494
+ !slot ||
495
+ !expected.operationId?.trim() ||
496
+ !expected.registrationGeneration?.trim() ||
497
+ expected.sessionId !== authority.sessionId ||
498
+ expected.sourceUpdateIds.length !== 1 ||
499
+ !Number.isSafeInteger(expected.sourceUpdateIds[0]) ||
500
+ expected.sourceUpdateIds[0] < 0 ||
501
+ typeof authority.generation !== "number" ||
502
+ !Number.isSafeInteger(authority.generation) ||
503
+ authority.generation < 0 ||
504
+ !authority.profileBindingKey?.trim() ||
505
+ !authority.cwd?.trim() ||
506
+ !authority.executor?.instanceId?.trim() ||
507
+ !authority.executor.leaderEpoch?.trim() ||
508
+ !Number.isSafeInteger(authority.operatorUserId) ||
509
+ authority.operatorUserId <= 0 ||
510
+ expected.target.chatId !== authority.operatorUserId ||
511
+ !Number.isSafeInteger(expected.target.threadId) ||
512
+ expected.target.threadId <= 0 ||
513
+ !oldTarget ||
514
+ oldTarget.chatId !== expected.target.chatId ||
515
+ !Number.isSafeInteger(oldTarget.threadId) ||
516
+ oldTarget.threadId <= 0)
517
+ return undefined;
518
+ const portsCurrent = () => deps.instanceId === instanceId &&
519
+ deps.getContextAuthority === getAuthority &&
520
+ deps.readLiveRebindIntent === read &&
521
+ deps.topicTargetStore === store &&
522
+ store.withWorkspaceLiveRebindSnapshot === snapshot &&
523
+ deps.registrationState === state &&
524
+ Object.entries(statePorts).every(([key, value]) => Reflect.get(state, key) === value);
525
+ const isCurrent = () => {
526
+ try {
527
+ if (!portsCurrent() ||
528
+ !isDeepStrictEqual(getAuthority.call(deps, ctx), authority) ||
529
+ state.getGeneration() !== expected.registrationGeneration ||
530
+ state.getSlot() !== slot ||
531
+ !isDeepStrictEqual(state.getLeaderProtocol(), authority.leaderProtocol) ||
532
+ ![
533
+ ...TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES,
534
+ ...TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES,
535
+ ].every((cap) => hasTelegramBusCapability(authority.leaderProtocol, cap)) ||
536
+ ![oldTarget, expected.target].some((target) => isDeepStrictEqual(state.getTarget(), target)))
537
+ return false;
538
+ return (portsCurrent() &&
539
+ isDeepStrictEqual(getAuthority.call(deps, ctx), authority) &&
540
+ state.getGeneration() === expected.registrationGeneration &&
541
+ state.getSlot() === slot &&
542
+ isDeepStrictEqual(state.getLeaderProtocol(), authority.leaderProtocol) &&
543
+ [oldTarget, expected.target].some((target) => isDeepStrictEqual(state.getTarget(), target)) &&
544
+ portsCurrent());
545
+ }
546
+ catch {
547
+ return false;
548
+ }
549
+ };
550
+ if (!isCurrent())
551
+ return undefined;
552
+ return {
553
+ isCurrent,
554
+ assertRecipientCurrent() {
555
+ if (!isCurrent())
556
+ throw new Error("Selected command recipient authority changed.");
557
+ const value = read.call(deps, expected.operationId, authority.profileBindingKey);
558
+ if (!value ||
559
+ value.kind !== "live-rebind" ||
560
+ value.phase !== "released" ||
561
+ value.cleanup ||
562
+ value.request.operationId !== expected.operationId ||
563
+ value.recipient.kind !== "follower" ||
564
+ value.recipient.instanceId !== instanceId ||
565
+ value.recipient.sessionId !== expected.sessionId ||
566
+ value.recipient.generation !== expected.registrationGeneration ||
567
+ !isDeepStrictEqual(value.executor, authority.executor) ||
568
+ value.operatorUserId !== authority.operatorUserId ||
569
+ !isDeepStrictEqual(value.request.source.updateIds, expected.sourceUpdateIds) ||
570
+ !isDeepStrictEqual(value.request.target, expected.target) ||
571
+ !isDeepStrictEqual(value.request.binding.target, oldTarget) ||
572
+ value.request.owner.instanceId !== instanceId ||
573
+ value.request.owner.owner?.kind !== "manual-follower" ||
574
+ value.request.owner.slot !== slot ||
575
+ !isDeepStrictEqual(value.request.owner.target, oldTarget) ||
576
+ value.request.binding.cwd !==
577
+ WorkspaceIdentity.normalizeTelegramWorkspacePath(authority.cwd) ||
578
+ value.request.binding.sessionId !== expected.sessionId ||
579
+ value.request.binding.slot !== slot ||
580
+ !isCurrent())
581
+ throw new Error("Selected command recipient has no exact released intent.");
582
+ const intent = structuredClone(value);
583
+ let confirmed = false;
584
+ snapshot.call(store, intent, (canonical) => {
585
+ if (!isCurrent() ||
586
+ !isDeepStrictEqual(read.call(deps, expected.operationId, authority.profileBindingKey), intent))
587
+ return;
588
+ const bindings = (canonical.workspaceBindings ?? []).filter((row) => row.bindingKey === intent.request.binding.bindingKey);
589
+ const owners = canonical.threads.filter((row) => row.status === "active" &&
590
+ (row.slot === slot ||
591
+ isDeepStrictEqual(row.target, expected.target)));
592
+ const row = bindings[0], owner = owners[0];
593
+ confirmed =
594
+ bindings.length === 1 &&
595
+ row?.sessionId === expected.sessionId &&
596
+ row.cwd === intent.request.binding.cwd &&
597
+ row.slot === slot &&
598
+ row.workspaceKey === intent.request.binding.workspaceKey &&
599
+ row.inactiveSinceMs === undefined &&
600
+ isDeepStrictEqual(row.target, expected.target) &&
601
+ owners.length === 1 &&
602
+ owner?.instanceId === instanceId &&
603
+ owner.owner?.kind === "manual-follower" &&
604
+ owner.slot === slot &&
605
+ owner.profileKey === intent.request.owner.profileKey &&
606
+ isDeepStrictEqual(owner.owner, intent.request.owner.owner) &&
607
+ isDeepStrictEqual(owner.target, expected.target) &&
608
+ isDeepStrictEqual(state.getTarget(), expected.target) &&
609
+ isCurrent() &&
610
+ isDeepStrictEqual(read.call(deps, expected.operationId, authority.profileBindingKey), intent);
611
+ });
612
+ if (!confirmed || !isCurrent())
613
+ throw new Error("Selected command canonical/local recipient changed.");
614
+ },
615
+ };
616
+ },
617
+ });
328
618
  }
329
619
  export function createTelegramBusFollowerClientRuntime(deps) {
330
620
  const createRequestId = createTelegramBusRequestIdFactory(deps.instanceId);
@@ -370,22 +660,14 @@ export function createTelegramBusFollowerQueueHandoffClient(deps) {
370
660
  return async (input) => {
371
661
  const registration = await resolveTelegramBusFollowerRegistration(deps, timeoutMs);
372
662
  const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
373
- const response = await sendTelegramBusLocalEnvelope({
374
- socketPath,
375
- timeoutMs: registration.remainingTimeoutMs,
376
- retry: getTelegramBusTransportRetryPolicy({
377
- endpoint: socketPath,
378
- operation: "operation",
379
- }),
380
- envelope: {
381
- kind: "follower.offerQueueHandoff",
382
- requestId: deps.createRequestId(),
383
- auth: deps.getAuthSecret?.(),
384
- instanceId: deps.instanceId,
385
- registrationGeneration: registration.generation,
386
- ...input,
387
- sentAtMs: getNowMs(),
388
- },
663
+ const response = await sendTelegramBusOperation(socketPath, registration.remainingTimeoutMs, {
664
+ kind: "follower.offerQueueHandoff",
665
+ requestId: deps.createRequestId(),
666
+ auth: deps.getAuthSecret?.(),
667
+ instanceId: deps.instanceId,
668
+ registrationGeneration: registration.generation,
669
+ ...input,
670
+ sentAtMs: getNowMs(),
389
671
  });
390
672
  const queueOwner = response?.kind === "bus.ack" && isRecord(response.result)
391
673
  ? parseTelegramUpdateJournalQueueOwner(response.result.queueOwner)
@@ -402,7 +684,8 @@ export function createTelegramBusFollowerQueueHandoffClient(deps) {
402
684
  input.payload.admissionReceipts[0]?.receiptId &&
403
685
  response.result.sourceUpdateIds.length ===
404
686
  input.payload.admissionReceipts[0].sourceUpdateIds.length &&
405
- response.result.sourceUpdateIds.every((updateId, index) => updateId === input.payload.admissionReceipts[0].sourceUpdateIds[index]) &&
687
+ response.result.sourceUpdateIds.every((updateId, index) => updateId ===
688
+ input.payload.admissionReceipts[0].sourceUpdateIds[index]) &&
406
689
  queueOwner) {
407
690
  return {
408
691
  status: "staged",
@@ -412,7 +695,7 @@ export function createTelegramBusFollowerQueueHandoffClient(deps) {
412
695
  };
413
696
  }
414
697
  throw new Error(response?.kind === "bus.ack"
415
- ? response.message ?? "Telegram queue handoff was rejected."
698
+ ? (response.message ?? "Telegram queue handoff was rejected.")
416
699
  : "Telegram queue handoff did not return an acknowledgement.");
417
700
  };
418
701
  }
@@ -421,19 +704,11 @@ function createTelegramBusAgentMessageClient(deps) {
421
704
  const timeoutMs = deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
422
705
  const request = async (envelope, requestTimeoutMs = timeoutMs) => {
423
706
  const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
424
- const response = await sendTelegramBusLocalEnvelope({
425
- socketPath,
426
- timeoutMs: requestTimeoutMs,
427
- retry: getTelegramBusTransportRetryPolicy({
428
- endpoint: socketPath,
429
- operation: "operation",
430
- }),
431
- envelope,
432
- });
707
+ const response = await sendTelegramBusOperation(socketPath, requestTimeoutMs, envelope);
433
708
  if (response?.kind === "bus.ack" && response.ok)
434
709
  return response.result;
435
710
  throw new Error(response?.kind === "bus.ack"
436
- ? response.message ?? "Telegram bus agent message failed."
711
+ ? (response.message ?? "Telegram bus agent message failed.")
437
712
  : "Telegram bus agent message did not return an acknowledgement.");
438
713
  };
439
714
  const registrationFields = async () => {
@@ -479,6 +754,151 @@ function createTelegramBusAgentMessageClient(deps) {
479
754
  },
480
755
  };
481
756
  }
757
+ /** Private selected text-effect caller; not a guarded ordinary API adapter or follower command admission grant. */
758
+ export function createTelegramBusFollowerSelectedMenuCaller(deps) {
759
+ return async (input) => {
760
+ let dispatched = false, method = "sendMessage";
761
+ try {
762
+ const client = { ...deps.client }, recipient = deps.recipient, ctx = input.ctx, assertAuthority = input.assertAuthority;
763
+ const getAuthority = recipient.getContextAuthority, getJournal = recipient.getJournalBindingKey, getProcess = recipient.getProcessIdentity;
764
+ const state = recipient.registrationState, statePorts = {
765
+ getTarget: state.getTarget,
766
+ getSlot: state.getSlot,
767
+ getGeneration: state.getGeneration,
768
+ getLeaderProtocol: state.getLeaderProtocol,
769
+ };
770
+ const authority = structuredClone(getAuthority.call(recipient, ctx));
771
+ const journalBindingKey = getJournal.call(recipient, ctx), process = structuredClone(getProcess.call(recipient)), target = structuredClone(state.getTarget()), slot = state.getSlot();
772
+ const protocol = structuredClone(deps.protocolIdentity), leaderProtocol = structuredClone(state.getLeaderProtocol()), auth = client.getAuthSecret?.();
773
+ const endpoint = resolveTelegramBusSocketPath(client.socketPath), timeoutMs = client.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
774
+ if (!authority ||
775
+ typeof authority.generation !== "number" ||
776
+ typeof assertAuthority !== "function" ||
777
+ !slot ||
778
+ !auth ||
779
+ !Number.isFinite(timeoutMs) ||
780
+ timeoutMs <= 0)
781
+ throw new Error("Selected-menu local owner is unavailable.");
782
+ const decoded = parseTelegramBusEnvelope(JSON.stringify({
783
+ kind: "follower.deliverSelectedMenu",
784
+ requestId: client.createRequestId(),
785
+ auth,
786
+ instanceId: client.instanceId,
787
+ operationId: input.operationId,
788
+ registrationGeneration: input.registrationGeneration,
789
+ recipient: {
790
+ sessionId: authority.sessionId,
791
+ sessionGeneration: authority.generation,
792
+ ...process,
793
+ profileKey: authority.profileBindingKey,
794
+ journalBindingKey,
795
+ target,
796
+ },
797
+ executor: authority.executor,
798
+ operatorUserId: authority.operatorUserId,
799
+ effect: structuredClone(input.effect),
800
+ sentAtMs: (client.getNowMs ?? Date.now)(),
801
+ }));
802
+ if (decoded?.kind !== "follower.deliverSelectedMenu")
803
+ throw new Error("Selected-menu local description is invalid.");
804
+ const expected = decoded;
805
+ method =
806
+ expected.effect.kind === "send-text"
807
+ ? "sendMessage"
808
+ : "editMessageText";
809
+ const assertCurrent = (allowPending = false) => {
810
+ assertAuthority();
811
+ const local = getAuthority.call(recipient, ctx), currentGeneration = client.getRegistrationGeneration();
812
+ if (!isDeepStrictEqual(deps.client, client) ||
813
+ deps.recipient !== recipient ||
814
+ recipient.getContextAuthority !== getAuthority ||
815
+ recipient.getJournalBindingKey !== getJournal ||
816
+ recipient.getProcessIdentity !== getProcess ||
817
+ recipient.registrationState !== state ||
818
+ !Object.entries(statePorts).every(([key, value]) => state[key] === value) ||
819
+ !isDeepStrictEqual(local, authority) ||
820
+ getJournal.call(recipient, ctx) !== journalBindingKey ||
821
+ !isDeepStrictEqual(getProcess.call(recipient), process) ||
822
+ !isDeepStrictEqual(state.getTarget(), target) ||
823
+ state.getSlot() !== slot ||
824
+ state.getGeneration() !== expected.registrationGeneration ||
825
+ (!allowPending &&
826
+ currentGeneration !== expected.registrationGeneration) ||
827
+ (currentGeneration !== undefined &&
828
+ currentGeneration !== expected.registrationGeneration) ||
829
+ client.getAuthSecret?.() !== auth ||
830
+ resolveTelegramBusSocketPath(client.socketPath) !== endpoint ||
831
+ !isDeepStrictEqual(deps.protocolIdentity, protocol) ||
832
+ !isDeepStrictEqual(state.getLeaderProtocol(), leaderProtocol) ||
833
+ !getTelegramBusProtocolCompatibility({
834
+ local: protocol,
835
+ remote: leaderProtocol,
836
+ }).compatible ||
837
+ !hasTelegramBusCapability(protocol, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY) ||
838
+ !hasTelegramBusCapability(leaderProtocol, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY))
839
+ throw new Error("Selected-menu local authority changed.");
840
+ if (!isDeepStrictEqual(getAuthority.call(recipient, ctx), authority))
841
+ throw new Error("Selected-menu context changed after local observations.");
842
+ assertAuthority();
843
+ };
844
+ assertCurrent(true);
845
+ const registration = await resolveTelegramBusFollowerRegistration(client, timeoutMs);
846
+ if (registration.generation !== expected.registrationGeneration)
847
+ throw new Error("Selected-menu registration changed.");
848
+ assertCurrent();
849
+ dispatched = true;
850
+ const response = await sendTelegramBusLocalEnvelope({
851
+ socketPath: endpoint,
852
+ envelope: expected,
853
+ timeoutMs: registration.remainingTimeoutMs,
854
+ retry: { attempts: 1, delayMs: 0 },
855
+ assertAuthority: () => assertCurrent(),
856
+ });
857
+ assertCurrent();
858
+ if (response?.kind !== "bus.ack" ||
859
+ response.requestId !== expected.requestId ||
860
+ !response.ok ||
861
+ response.error !== undefined ||
862
+ (response.protocol !== undefined &&
863
+ !isDeepStrictEqual(response.protocol, leaderProtocol)) ||
864
+ !isRecord(response.result))
865
+ throw new Error("Selected-menu acknowledgement is unconfirmed.");
866
+ const result = response.result;
867
+ if (!isDeepStrictEqual(Object.keys(result).sort(), [
868
+ "effect",
869
+ "messageId",
870
+ "operationId",
871
+ "recipient",
872
+ "registrationGeneration",
873
+ ]) ||
874
+ result.operationId !== expected.operationId ||
875
+ result.registrationGeneration !== expected.registrationGeneration ||
876
+ result.effect !== expected.effect.kind ||
877
+ !isDeepStrictEqual(result.recipient, expected.recipient) ||
878
+ typeof result.messageId !== "number" ||
879
+ !Number.isSafeInteger(result.messageId) ||
880
+ result.messageId <= 0 ||
881
+ (expected.effect.kind === "edit-text" &&
882
+ result.messageId !== expected.effect.messageId))
883
+ throw new Error("Selected-menu recipient/result is unconfirmed.");
884
+ assertCurrent();
885
+ return {
886
+ operationId: expected.operationId,
887
+ recipient: structuredClone(expected.recipient),
888
+ registrationGeneration: expected.registrationGeneration,
889
+ effect: expected.effect.kind,
890
+ messageId: result.messageId,
891
+ };
892
+ }
893
+ catch (error) {
894
+ if (!dispatched ||
895
+ (error instanceof TelegramBusLocalAuthorityError &&
896
+ !error.requestIssued))
897
+ throw new TelegramApiAuthorityError(false);
898
+ throw new TelegramApiCommitUnknownError(method, error);
899
+ }
900
+ };
901
+ }
482
902
  export function createTelegramBusFollowerApiCaller(deps) {
483
903
  const getNowMs = deps.getNowMs ?? Date.now;
484
904
  const timeoutMs = deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
@@ -487,23 +907,15 @@ export function createTelegramBusFollowerApiCaller(deps) {
487
907
  const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
488
908
  let response;
489
909
  try {
490
- response = await sendTelegramBusLocalEnvelope({
491
- socketPath,
492
- timeoutMs: registration.remainingTimeoutMs,
493
- retry: getTelegramBusTransportRetryPolicy({
494
- endpoint: socketPath,
495
- operation: "operation",
496
- }),
497
- envelope: {
498
- kind: "follower.callApi",
499
- requestId: deps.createRequestId(),
500
- auth: deps.getAuthSecret?.(),
501
- instanceId: deps.instanceId,
502
- registrationGeneration: registration.generation,
503
- method,
504
- args,
505
- sentAtMs: getNowMs(),
506
- },
910
+ response = await sendTelegramBusOperation(socketPath, registration.remainingTimeoutMs, {
911
+ kind: "follower.callApi",
912
+ requestId: deps.createRequestId(),
913
+ auth: deps.getAuthSecret?.(),
914
+ instanceId: deps.instanceId,
915
+ registrationGeneration: registration.generation,
916
+ method,
917
+ args,
918
+ sentAtMs: getNowMs(),
507
919
  });
508
920
  }
509
921
  catch (error) {
@@ -550,11 +962,6 @@ async function resolveTelegramBusFollowerRegistration(deps, timeoutMs) {
550
962
  }
551
963
  throw new Error("Telegram bus follower is not registered.");
552
964
  }
553
- function isTelegramStaleContextError(error) {
554
- return (error instanceof Error &&
555
- (error.message.includes("stale after session") ||
556
- error.message.includes("stale ctx")));
557
- }
558
965
  export function createTelegramBusFollowerSessionReplacementSuspender(deps) {
559
966
  const getNowMs = deps.getNowMs ?? Date.now;
560
967
  const getPid = deps.getPid ?? (() => process.pid);
@@ -661,7 +1068,9 @@ export function createTelegramBusFollowerSessionRefreshHook(deps) {
661
1068
  await deps.registrationRuntime.setContext(ctx);
662
1069
  }
663
1070
  catch (error) {
664
- deps.recordRuntimeEvent("bus", error, { phase: "follower-session-refresh" });
1071
+ deps.recordRuntimeEvent("bus", error, {
1072
+ phase: "follower-session-refresh",
1073
+ });
665
1074
  return;
666
1075
  }
667
1076
  if (deps.isSessionActive && !deps.isSessionActive(ctx))
@@ -710,14 +1119,20 @@ export function createTelegramBusFollowerRegistrationState(options = {}) {
710
1119
  getThreadName: () => threadName,
711
1120
  getDisplayTitle: () => displayTitle,
712
1121
  setDisplayTitle(title, expectedGeneration) {
713
- if (!registered || !generation || expectedGeneration !== generation ||
714
- !title.trim() || title.length > 128 || displayTitle === title)
1122
+ if (!registered ||
1123
+ !generation ||
1124
+ expectedGeneration !== generation ||
1125
+ !title.trim() ||
1126
+ title.length > 128 ||
1127
+ displayTitle === title)
715
1128
  return false;
716
1129
  displayTitle = title;
717
1130
  return true;
718
1131
  },
719
1132
  getGeneration: () => generation,
720
- getSessionId: currentSessionId => registered && generation && currentSessionId === sessionId ? sessionId : undefined,
1133
+ getSessionId: (currentSessionId) => registered && generation && currentSessionId === sessionId
1134
+ ? sessionId
1135
+ : undefined,
721
1136
  beginRecovery: () => {
722
1137
  if (activeRecoveryEpoch !== undefined)
723
1138
  return activeRecoveryEpoch;
@@ -757,21 +1172,31 @@ export function createTelegramBusFollowerRegistrationState(options = {}) {
757
1172
  eligibleElectionSlots = Array.from(new Set(slots.filter((slot) => /^[A-Z]$/.test(slot)))).sort();
758
1173
  },
759
1174
  setRegistered: (next, nextTarget, metadata) => {
760
- const retainedDisplayTitle = next && registered &&
1175
+ const retainedDisplayTitle = next &&
1176
+ registered &&
761
1177
  generation === metadata?.generation &&
762
- target?.chatId === nextTarget?.chatId && target?.threadId === nextTarget?.threadId
763
- ? displayTitle : undefined;
764
- const retainedSessionId = next && registered && generation === metadata?.generation ? sessionId : undefined;
1178
+ target?.chatId === nextTarget?.chatId &&
1179
+ target?.threadId === nextTarget?.threadId
1180
+ ? displayTitle
1181
+ : undefined;
1182
+ const retainedSessionId = next && registered && generation === metadata?.generation
1183
+ ? sessionId
1184
+ : undefined;
765
1185
  const availabilityChanged = registered !== next;
766
1186
  registered = next;
767
1187
  target = next ? (nextTarget ? { ...nextTarget } : undefined) : undefined;
768
1188
  slot = next ? metadata?.slot : undefined;
769
1189
  threadName = next ? metadata?.threadName : undefined;
770
- displayTitle = next && nextTarget && metadata?.generation &&
771
- metadata.displayTitle?.trim() && metadata.displayTitle.length <= 128
772
- ? metadata.displayTitle : retainedDisplayTitle;
1190
+ displayTitle =
1191
+ next &&
1192
+ nextTarget &&
1193
+ metadata?.generation &&
1194
+ metadata.displayTitle?.trim() &&
1195
+ metadata.displayTitle.length <= 128
1196
+ ? metadata.displayTitle
1197
+ : retainedDisplayTitle;
773
1198
  generation = next ? metadata?.generation : undefined;
774
- sessionId = next ? metadata?.sessionId ?? retainedSessionId : undefined;
1199
+ sessionId = next ? (metadata?.sessionId ?? retainedSessionId) : undefined;
775
1200
  leaderProtocol =
776
1201
  next && metadata?.leaderProtocol
777
1202
  ? {
@@ -803,7 +1228,7 @@ export function createTelegramBusFollowerHeartbeatRecoveryHandler(deps) {
803
1228
  deps.updateStatus(ctx);
804
1229
  }
805
1230
  catch (error) {
806
- if (!isTelegramStaleContextError(error))
1231
+ if (!isPiStaleContextError(error))
807
1232
  throw error;
808
1233
  deps.recordRuntimeEvent("bus", error, {
809
1234
  phase: "follower-stale-context-status",
@@ -950,7 +1375,7 @@ export function createTelegramBusFollowerHeartbeatRecoveryHandler(deps) {
950
1375
  catch (promotionError) {
951
1376
  deps.setLifecyclePhase(undefined);
952
1377
  safeUpdateStatus(ctx);
953
- if (isTelegramStaleContextError(promotionError)) {
1378
+ if (isPiStaleContextError(promotionError)) {
954
1379
  deps.recordRuntimeEvent("bus", promotionError, {
955
1380
  phase: "follower-heartbeat-stale-context",
956
1381
  });
@@ -990,6 +1415,11 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
990
1415
  let lastKnownTarget;
991
1416
  let lastKnownSlot;
992
1417
  let lastKnownThreadName;
1418
+ // A leader result is accepted only for the same endpoint and registration generation that issued the request.
1419
+ const isLeaderSessionCurrent = (socketPath, generation) => activeLeaderSocketPath === socketPath &&
1420
+ activeRegistrationGeneration === generation &&
1421
+ (!deps.registrationState ||
1422
+ deps.registrationState.getGeneration() === generation);
993
1423
  const stopHeartbeat = () => {
994
1424
  if (!heartbeatInterval)
995
1425
  return;
@@ -1031,21 +1461,13 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1031
1461
  activeRegistrationGeneration === registrationGeneration &&
1032
1462
  activeContext === heartbeatContext;
1033
1463
  try {
1034
- const response = await sendTelegramBusLocalEnvelope({
1035
- socketPath: leaderSocketPath,
1036
- timeoutMs: heartbeatTimeoutMs,
1037
- retry: getTelegramBusTransportRetryPolicy({
1038
- endpoint: leaderSocketPath,
1039
- operation: "operation",
1040
- }),
1041
- envelope: {
1042
- kind: "follower.heartbeat",
1043
- requestId: deps.createRequestId(),
1044
- auth: activeAuthSecret,
1045
- instanceId: deps.instanceId,
1046
- registrationGeneration,
1047
- sentAtMs: getNowMs(),
1048
- },
1464
+ const response = await sendTelegramBusOperation(leaderSocketPath, heartbeatTimeoutMs, {
1465
+ kind: "follower.heartbeat",
1466
+ requestId: deps.createRequestId(),
1467
+ auth: activeAuthSecret,
1468
+ instanceId: deps.instanceId,
1469
+ registrationGeneration,
1470
+ sentAtMs: getNowMs(),
1049
1471
  });
1050
1472
  if (!isCurrentHeartbeat())
1051
1473
  return;
@@ -1133,7 +1555,8 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1133
1555
  const attempt = { ctx, sessionGeneration, sessionId };
1134
1556
  registrationAttempt = attempt;
1135
1557
  const isCurrentRequest = () => registrationAttempt === attempt &&
1136
- deps.getSessionGeneration?.() === sessionGeneration && deps.getSessionId?.(ctx) === sessionId &&
1558
+ deps.getSessionGeneration?.() === sessionGeneration &&
1559
+ deps.getSessionId?.(ctx) === sessionId &&
1137
1560
  deps.isContextActive?.(ctx) !== false;
1138
1561
  const abandonOwnedRequest = async () => {
1139
1562
  if (registrationAttempt !== attempt)
@@ -1148,6 +1571,17 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1148
1571
  deps.setActiveAuthSecret?.(undefined);
1149
1572
  await deps.stopReceiving?.();
1150
1573
  };
1574
+ // Failed registration releases its leader session but keeps the attempt owner for caller-visible errors.
1575
+ const failRegistration = async (clearGeneration = false) => {
1576
+ stopHeartbeat();
1577
+ activeLeaderSocketPath = undefined;
1578
+ activeAuthSecret = undefined;
1579
+ if (clearGeneration)
1580
+ activeRegistrationGeneration = undefined;
1581
+ deps.registrationState?.setRegistered(false);
1582
+ deps.setActiveAuthSecret?.(undefined);
1583
+ await deps.stopReceiving?.();
1584
+ };
1151
1585
  const pendingHandoff = options
1152
1586
  ? undefined
1153
1587
  : getTelegramFollowerSessionHandoff();
@@ -1232,12 +1666,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1232
1666
  await abandonOwnedRequest();
1233
1667
  return false;
1234
1668
  }
1235
- stopHeartbeat();
1236
- activeLeaderSocketPath = undefined;
1237
- activeAuthSecret = undefined;
1238
- deps.registrationState?.setRegistered(false);
1239
- deps.setActiveAuthSecret?.(undefined);
1240
- await deps.stopReceiving?.();
1669
+ await failRegistration();
1241
1670
  throw error;
1242
1671
  }
1243
1672
  if (!isCurrentRequest()) {
@@ -1249,23 +1678,15 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1249
1678
  remote: response?.kind === "bus.ack" ? response.protocol : undefined,
1250
1679
  });
1251
1680
  // An identity-less error ACK is a rejection/transport failure, not evidence of an incompatible leader.
1252
- const unidentifiedRejection = response?.kind === "bus.ack" && !response.ok && response.protocol === undefined;
1681
+ const unidentifiedRejection = response?.kind === "bus.ack" &&
1682
+ !response.ok &&
1683
+ response.protocol === undefined;
1253
1684
  if (!compatibility.compatible && !unidentifiedRejection) {
1254
- stopHeartbeat();
1255
- activeLeaderSocketPath = undefined;
1256
- activeAuthSecret = undefined;
1257
- deps.registrationState?.setRegistered(false);
1258
- deps.setActiveAuthSecret?.(undefined);
1259
- await deps.stopReceiving?.();
1685
+ await failRegistration();
1260
1686
  throw new Error(`Incompatible Telegram bus leader protocol: ${compatibility.reason}.`);
1261
1687
  }
1262
1688
  if (response?.kind === "bus.ack" && !response.ok) {
1263
- stopHeartbeat();
1264
- activeLeaderSocketPath = undefined;
1265
- activeAuthSecret = undefined;
1266
- deps.registrationState?.setRegistered(false);
1267
- deps.setActiveAuthSecret?.(undefined);
1268
- await deps.stopReceiving?.();
1689
+ await failRegistration();
1269
1690
  if (registrationOptions?.restoreWorkspace &&
1270
1691
  response.error?.code === "workspace-binding-unavailable") {
1271
1692
  deps.recordRuntimeEvent?.("bus", "Telegram follower auto-connect refused by leader: Workspace binding unavailable.", {
@@ -1283,9 +1704,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1283
1704
  ...registrationResult,
1284
1705
  generation: registrationGeneration,
1285
1706
  sessionId,
1286
- ...(response.protocol
1287
- ? { leaderProtocol: response.protocol }
1288
- : {}),
1707
+ ...(response.protocol ? { leaderProtocol: response.protocol } : {}),
1289
1708
  });
1290
1709
  lastKnownTarget = registrationResult.target;
1291
1710
  lastKnownSlot = registrationResult.slot;
@@ -1301,13 +1720,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1301
1720
  await abandonOwnedRequest();
1302
1721
  return false;
1303
1722
  }
1304
- stopHeartbeat();
1305
- activeLeaderSocketPath = undefined;
1306
- activeAuthSecret = undefined;
1307
- activeRegistrationGeneration = undefined;
1308
- deps.registrationState?.setRegistered(false);
1309
- deps.setActiveAuthSecret?.(undefined);
1310
- await deps.stopReceiving?.();
1723
+ await failRegistration(true);
1311
1724
  throw error;
1312
1725
  }
1313
1726
  if (!isCurrentRequest()) {
@@ -1327,18 +1740,15 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1327
1740
  }
1328
1741
  return true;
1329
1742
  }
1330
- stopHeartbeat();
1331
- activeLeaderSocketPath = undefined;
1332
- activeAuthSecret = undefined;
1333
- deps.registrationState?.setRegistered(false);
1334
- deps.setActiveAuthSecret?.(undefined);
1335
- await deps.stopReceiving?.();
1743
+ await failRegistration();
1336
1744
  return false;
1337
1745
  },
1338
1746
  setContext(ctx) {
1339
- if (registrationAttempt && (registrationAttempt.ctx !== ctx ||
1340
- registrationAttempt.sessionGeneration !== deps.getSessionGeneration?.() ||
1341
- registrationAttempt.sessionId !== deps.getSessionId?.(ctx)))
1747
+ if (registrationAttempt &&
1748
+ (registrationAttempt.ctx !== ctx ||
1749
+ registrationAttempt.sessionGeneration !==
1750
+ deps.getSessionGeneration?.() ||
1751
+ registrationAttempt.sessionId !== deps.getSessionId?.(ctx)))
1342
1752
  registrationAttempt = undefined;
1343
1753
  activeContext = ctx;
1344
1754
  },
@@ -1346,7 +1756,9 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1346
1756
  const socketPath = activeLeaderSocketPath;
1347
1757
  const generation = activeRegistrationGeneration;
1348
1758
  const auth = activeAuthSecret;
1349
- if (!socketPath || !generation || !deps.registrationState?.isRegistered()) {
1759
+ if (!socketPath ||
1760
+ !generation ||
1761
+ !deps.registrationState?.isRegistered()) {
1350
1762
  throw new Error("Telegram follower is not registered with the leader.");
1351
1763
  }
1352
1764
  const requiredCapability = mode === "directory-snake" || mode === "directory-title"
@@ -1358,20 +1770,31 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1358
1770
  }
1359
1771
  const requestId = deps.createRequestId();
1360
1772
  const response = await sendTelegramBusLocalEnvelope({
1361
- socketPath, timeoutMs: registrationTimeoutMs,
1773
+ socketPath,
1774
+ timeoutMs: registrationTimeoutMs,
1362
1775
  envelope: {
1363
- kind: "follower.setThreadDisplayMode", requestId, auth,
1364
- instanceId: deps.instanceId, registrationGeneration: generation, mode,
1776
+ kind: "follower.setThreadDisplayMode",
1777
+ requestId,
1778
+ auth,
1779
+ instanceId: deps.instanceId,
1780
+ registrationGeneration: generation,
1781
+ mode,
1365
1782
  },
1366
1783
  });
1367
- if (activeLeaderSocketPath !== socketPath || activeRegistrationGeneration !== generation ||
1368
- activeAuthSecret !== auth || deps.registrationState.getGeneration() !== generation) {
1784
+ if (activeLeaderSocketPath !== socketPath ||
1785
+ activeRegistrationGeneration !== generation ||
1786
+ activeAuthSecret !== auth ||
1787
+ deps.registrationState.getGeneration() !== generation) {
1369
1788
  throw new Error("Telegram Thread display setting completed for a stale registration.");
1370
1789
  }
1371
- if (response?.kind !== "bus.ack" || !response.ok || response.requestId !== requestId ||
1372
- !isRecord(response.result) || response.result.mode !== mode) {
1790
+ if (response?.kind !== "bus.ack" ||
1791
+ !response.ok ||
1792
+ response.requestId !== requestId ||
1793
+ !isRecord(response.result) ||
1794
+ response.result.mode !== mode) {
1373
1795
  throw new Error(response?.kind === "bus.ack"
1374
- ? response.message ?? "Telegram Thread display setting was rejected."
1796
+ ? (response.message ??
1797
+ "Telegram Thread display setting was rejected.")
1375
1798
  : "Telegram Thread display setting was not acknowledged.");
1376
1799
  }
1377
1800
  },
@@ -1386,34 +1809,23 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1386
1809
  const expectedLeaderSocketPath = activeLeaderSocketPath;
1387
1810
  const expectedRegistrationGeneration = activeRegistrationGeneration;
1388
1811
  const expectedAuthSecret = activeAuthSecret;
1389
- const response = await sendTelegramBusLocalEnvelope({
1390
- socketPath: expectedLeaderSocketPath,
1391
- timeoutMs: registrationTimeoutMs,
1392
- retry: getTelegramBusTransportRetryPolicy({
1393
- endpoint: expectedLeaderSocketPath,
1394
- operation: "operation",
1395
- }),
1396
- envelope: {
1397
- kind: "follower.renameThread",
1398
- requestId: deps.createRequestId(),
1399
- auth: expectedAuthSecret,
1400
- instanceId: deps.instanceId,
1401
- registrationGeneration: expectedRegistrationGeneration,
1402
- target,
1403
- threadName,
1404
- sentAtMs: getNowMs(),
1405
- },
1812
+ const response = await sendTelegramBusOperation(expectedLeaderSocketPath, registrationTimeoutMs, {
1813
+ kind: "follower.renameThread",
1814
+ requestId: deps.createRequestId(),
1815
+ auth: expectedAuthSecret,
1816
+ instanceId: deps.instanceId,
1817
+ registrationGeneration: expectedRegistrationGeneration,
1818
+ target,
1819
+ threadName,
1820
+ sentAtMs: getNowMs(),
1406
1821
  });
1407
1822
  if (response?.kind !== "bus.ack" || !response.ok) {
1408
1823
  throw new Error(response?.kind === "bus.ack"
1409
- ? (response.message ?? "Telegram Workspace Thread rename was rejected.")
1824
+ ? (response.message ??
1825
+ "Telegram Workspace Thread rename was rejected.")
1410
1826
  : "Telegram Workspace Thread rename was not acknowledged.");
1411
1827
  }
1412
- if (activeLeaderSocketPath !== expectedLeaderSocketPath ||
1413
- activeRegistrationGeneration !== expectedRegistrationGeneration ||
1414
- (deps.registrationState &&
1415
- deps.registrationState.getGeneration() !==
1416
- expectedRegistrationGeneration)) {
1828
+ if (!isLeaderSessionCurrent(expectedLeaderSocketPath, expectedRegistrationGeneration)) {
1417
1829
  throw new Error("Telegram Workspace Thread rename completed for a stale follower registration.");
1418
1830
  }
1419
1831
  const renamedThreadName = response.result &&
@@ -1437,42 +1849,36 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1437
1849
  if (!activeLeaderSocketPath || !activeRegistrationGeneration) {
1438
1850
  throw new Error("Telegram follower is not registered with the leader.");
1439
1851
  }
1440
- if (deps.registrationState && !hasTelegramBusCapability(deps.registrationState.getLeaderProtocol(), TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME)) {
1852
+ if (deps.registrationState &&
1853
+ !hasTelegramBusCapability(deps.registrationState.getLeaderProtocol(), TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME)) {
1441
1854
  throw new Error("The active Telegram leader does not support Workspace Thread reset. Update or restart that Pi instance.");
1442
1855
  }
1443
1856
  const expectedLeaderSocketPath = activeLeaderSocketPath;
1444
1857
  const expectedRegistrationGeneration = activeRegistrationGeneration;
1445
- const response = await sendTelegramBusLocalEnvelope({
1446
- socketPath: expectedLeaderSocketPath,
1447
- timeoutMs: registrationTimeoutMs,
1448
- retry: getTelegramBusTransportRetryPolicy({
1449
- endpoint: expectedLeaderSocketPath,
1450
- operation: "operation",
1451
- }),
1452
- envelope: {
1453
- kind: "follower.resetThreadName",
1454
- requestId: deps.createRequestId(),
1455
- auth: activeAuthSecret,
1456
- instanceId: deps.instanceId,
1457
- registrationGeneration: expectedRegistrationGeneration,
1458
- target,
1459
- sentAtMs: getNowMs(),
1460
- },
1858
+ const response = await sendTelegramBusOperation(expectedLeaderSocketPath, registrationTimeoutMs, {
1859
+ kind: "follower.resetThreadName",
1860
+ requestId: deps.createRequestId(),
1861
+ auth: activeAuthSecret,
1862
+ instanceId: deps.instanceId,
1863
+ registrationGeneration: expectedRegistrationGeneration,
1864
+ target,
1865
+ sentAtMs: getNowMs(),
1461
1866
  });
1462
- const resetName = response?.kind === "bus.ack" && response.ok &&
1463
- response.result && typeof response.result === "object" &&
1867
+ const resetName = response?.kind === "bus.ack" &&
1868
+ response.ok &&
1869
+ response.result &&
1870
+ typeof response.result === "object" &&
1464
1871
  "threadName" in response.result &&
1465
1872
  typeof response.result.threadName === "string"
1466
- ? response.result.threadName : undefined;
1873
+ ? response.result.threadName
1874
+ : undefined;
1467
1875
  if (!resetName) {
1468
1876
  throw new Error(response?.kind === "bus.ack"
1469
- ? response.message ?? "Telegram Workspace Thread reset was rejected."
1877
+ ? (response.message ??
1878
+ "Telegram Workspace Thread reset was rejected.")
1470
1879
  : "Telegram Workspace Thread reset was not acknowledged.");
1471
1880
  }
1472
- if (activeLeaderSocketPath !== expectedLeaderSocketPath ||
1473
- activeRegistrationGeneration !== expectedRegistrationGeneration ||
1474
- (deps.registrationState && deps.registrationState.getGeneration() !==
1475
- expectedRegistrationGeneration)) {
1881
+ if (!isLeaderSessionCurrent(expectedLeaderSocketPath, expectedRegistrationGeneration)) {
1476
1882
  throw new Error("Telegram Workspace Thread reset completed for a stale follower registration.");
1477
1883
  }
1478
1884
  const registrationTarget = deps.registrationState?.getTarget();
@@ -1498,38 +1904,28 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1498
1904
  const expectedRegistrationGeneration = activeRegistrationGeneration;
1499
1905
  const expectedAuthSecret = activeAuthSecret;
1500
1906
  const requestId = deps.createRequestId();
1501
- const response = await sendTelegramBusLocalEnvelope({
1502
- socketPath: expectedLeaderSocketPath,
1503
- timeoutMs: registrationTimeoutMs,
1504
- retry: getTelegramBusTransportRetryPolicy({
1505
- endpoint: expectedLeaderSocketPath,
1506
- operation: "operation",
1507
- }),
1508
- envelope: {
1509
- kind: operation === "publish"
1510
- ? "follower.publishSessionReplacement"
1511
- : "follower.settleSessionReplacement",
1512
- requestId,
1513
- auth: expectedAuthSecret,
1514
- instanceId: deps.instanceId,
1515
- registrationGeneration: expectedRegistrationGeneration,
1516
- intent,
1517
- sentAtMs: getNowMs(),
1518
- },
1907
+ const response = await sendTelegramBusOperation(expectedLeaderSocketPath, registrationTimeoutMs, {
1908
+ kind: operation === "publish"
1909
+ ? "follower.publishSessionReplacement"
1910
+ : "follower.settleSessionReplacement",
1911
+ requestId,
1912
+ auth: expectedAuthSecret,
1913
+ instanceId: deps.instanceId,
1914
+ registrationGeneration: expectedRegistrationGeneration,
1915
+ intent,
1916
+ sentAtMs: getNowMs(),
1519
1917
  });
1520
- if (response?.kind !== "bus.ack" || !response.ok ||
1918
+ if (response?.kind !== "bus.ack" ||
1919
+ !response.ok ||
1521
1920
  response.requestId !== requestId ||
1522
- !isRecord(response.result) || response.result.committed !== true) {
1921
+ !isRecord(response.result) ||
1922
+ response.result.committed !== true) {
1523
1923
  throw new Error(response?.kind === "bus.ack"
1524
1924
  ? (response.message ?? "Telegram session replacement was rejected.")
1525
1925
  : "Telegram session replacement was not acknowledged.");
1526
1926
  }
1527
- if (activeLeaderSocketPath !== expectedLeaderSocketPath ||
1528
- activeRegistrationGeneration !== expectedRegistrationGeneration ||
1529
- activeAuthSecret !== expectedAuthSecret ||
1530
- (deps.registrationState &&
1531
- deps.registrationState.getGeneration() !==
1532
- expectedRegistrationGeneration)) {
1927
+ if (!isLeaderSessionCurrent(expectedLeaderSocketPath, expectedRegistrationGeneration) ||
1928
+ activeAuthSecret !== expectedAuthSecret) {
1533
1929
  throw new Error("Telegram session replacement completed for a stale follower registration.");
1534
1930
  }
1535
1931
  return true;
@@ -1538,21 +1934,13 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
1538
1934
  if (!activeLeaderSocketPath || !activeRegistrationGeneration) {
1539
1935
  return false;
1540
1936
  }
1541
- const response = await sendTelegramBusLocalEnvelope({
1542
- socketPath: activeLeaderSocketPath,
1543
- timeoutMs: registrationTimeoutMs,
1544
- retry: getTelegramBusTransportRetryPolicy({
1545
- endpoint: activeLeaderSocketPath,
1546
- operation: "operation",
1547
- }),
1548
- envelope: {
1549
- kind: "follower.disconnect",
1550
- requestId: deps.createRequestId(),
1551
- auth: activeAuthSecret,
1552
- instanceId: deps.instanceId,
1553
- registrationGeneration: activeRegistrationGeneration,
1554
- sentAtMs: getNowMs(),
1555
- },
1937
+ const response = await sendTelegramBusOperation(activeLeaderSocketPath, registrationTimeoutMs, {
1938
+ kind: "follower.disconnect",
1939
+ requestId: deps.createRequestId(),
1940
+ auth: activeAuthSecret,
1941
+ instanceId: deps.instanceId,
1942
+ registrationGeneration: activeRegistrationGeneration,
1943
+ sentAtMs: getNowMs(),
1556
1944
  });
1557
1945
  if (response?.kind === "bus.ack" && response.ok)
1558
1946
  return true;
@@ -1570,29 +1958,50 @@ export function createTelegramBusFollowerPairedAdmission(deps) {
1570
1958
  if (updates.length !== 1)
1571
1959
  return { admitted: false };
1572
1960
  const update = updates[0];
1573
- const keys = Object.keys(update).filter((key) => key !== "update_id" && key !== TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD);
1961
+ const keys = Object.keys(update).filter((key) => key !== "update_id" &&
1962
+ key !== TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD);
1574
1963
  if (keys.length !== 1)
1575
1964
  return { admitted: false };
1576
1965
  const kind = keys[0];
1577
- if (kind !== "message" && kind !== "edited_message" &&
1578
- kind !== "callback_query" && kind !== "message_reaction")
1966
+ if (kind !== "message" &&
1967
+ kind !== "edited_message" &&
1968
+ kind !== "callback_query" &&
1969
+ kind !== "message_reaction")
1579
1970
  return { admitted: false };
1580
1971
  const position = update[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
1581
- if (position !== undefined && (kind !== "message" ||
1582
- (position !== "comment" && position !== "forward")))
1972
+ if (position !== undefined &&
1973
+ (kind !== "message" || (position !== "comment" && position !== "forward")))
1583
1974
  return { admitted: false };
1584
1975
  const carrier = update[kind];
1585
- if (!Number.isSafeInteger(update.update_id) || update.update_id < 0 ||
1586
- !isRecord(carrier) || carrier.pi_telegram_source_update_id !== update.update_id ||
1587
- carrier.sender_chat !== undefined || carrier.actor_chat !== undefined)
1976
+ if (!Number.isSafeInteger(update.update_id) ||
1977
+ update.update_id < 0 ||
1978
+ !isRecord(carrier) ||
1979
+ carrier.pi_telegram_source_update_id !== update.update_id ||
1980
+ carrier.sender_chat !== undefined ||
1981
+ carrier.actor_chat !== undefined)
1588
1982
  return { admitted: false };
1589
1983
  const sender = kind === "message_reaction" ? carrier.user : carrier.from;
1590
- if (!isRecord(sender) || typeof sender.id !== "number" || !Number.isSafeInteger(sender.id) ||
1591
- sender.id <= 0 || sender.is_bot !== false)
1984
+ if (!isRecord(sender) ||
1985
+ typeof sender.id !== "number" ||
1986
+ !Number.isSafeInteger(sender.id) ||
1987
+ sender.id <= 0 ||
1988
+ sender.is_bot !== false)
1592
1989
  return { admitted: false };
1593
1990
  return deps.configStore.withPairedUserAdmission(deps.profileName, deps.tokenSha256, sender.id, publish, deps.assertExecutionCurrent);
1594
1991
  };
1595
1992
  }
1993
+ /** One authenticated local operation request with the shared operation retry policy; no replay semantics. */
1994
+ function sendTelegramBusOperation(socketPath, timeoutMs, envelope) {
1995
+ return sendTelegramBusLocalEnvelope({
1996
+ socketPath,
1997
+ timeoutMs,
1998
+ envelope,
1999
+ retry: getTelegramBusTransportRetryPolicy({
2000
+ endpoint: socketPath,
2001
+ operation: "operation",
2002
+ }),
2003
+ });
2004
+ }
1596
2005
  export function prepareTelegramBusFollowerJournaledUpdateForExecution(update, prepareForwardedMessage) {
1597
2006
  const position = update[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
1598
2007
  if (update.message !== undefined &&
@@ -1605,14 +2014,475 @@ export function prepareTelegramBusFollowerJournaledUpdateForExecution(update, pr
1605
2014
  delete prepared[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
1606
2015
  return prepared;
1607
2016
  }
2017
+ /** Copy the captured command target for the native owner; ordinary input supplies no target constraint. */
2018
+ function copyLiveRebindCommandTarget(saved) {
2019
+ return saved.selectedCommand
2020
+ ? { expectedTarget: { ...saved.selectedCommand.target } }
2021
+ : {};
2022
+ }
2023
+ /** Exact operation/recipient/source agreement between a retained save and a later request; identity only, never currency. */
2024
+ function matchesLiveRebindCarrier(saved, envelope) {
2025
+ return (saved.operationId === envelope.operationId &&
2026
+ saved.recipient.instanceId === envelope.recipientInstanceId &&
2027
+ saved.recipient.sessionId === envelope.recipientSessionId &&
2028
+ saved.recipient.generation === envelope.recipientRegistrationGeneration &&
2029
+ saved.recipient.bindingKey === envelope.recipientBindingKey &&
2030
+ isDeepStrictEqual(saved.sourceUpdateIds, [...envelope.sourceUpdateIds].sort((a, b) => a - b)));
2031
+ }
2032
+ /** Exact branch agreement: a command record requires its saved proof; ordinary input refuses any proof. */
2033
+ function matchesLiveRebindBranch(saved, status, envelope) {
2034
+ return (isDeepStrictEqual(saved.selectedCommand, envelope.selectedCommand) &&
2035
+ (status
2036
+ ? isDeepStrictEqual(saved.preparedSource, envelope.preparedSource)
2037
+ : envelope.preparedSource === undefined));
2038
+ }
2039
+ /** One current warm preparation, not a prompt queue or restart recovery record. */
2040
+ export function createTelegramBusFollowerLiveRebindRuntime(deps) {
2041
+ let retained;
2042
+ const runtime = {
2043
+ save(envelope, ctx, isCurrent) {
2044
+ const captured = structuredClone(envelope), selectedCommand = captured.selectedCommand;
2045
+ const heldMarker = selectedCommand, getCommandOwner = heldMarker ? deps.getCommandOwner : undefined, applyOwner = deps.applyTarget;
2046
+ if (heldMarker !== undefined &&
2047
+ (!getCommandOwner || !applyOwner || captured.updates.length !== 1))
2048
+ throw new Error("Selected command native consumption is unavailable.");
2049
+ const admission = deps.getAdmission(ctx), prepareInput = admission?.prepareLiveInput, append = admission?.appendBatch;
2050
+ let status = retained?.operationId === captured.operationId
2051
+ ? retained.status
2052
+ : undefined;
2053
+ const current = () => isCurrent() &&
2054
+ deps.getAdmission(ctx) === admission &&
2055
+ admission?.getJournalBindingKey() === captured.recipientBindingKey &&
2056
+ (!heldMarker ||
2057
+ (deps.getCommandOwner === getCommandOwner &&
2058
+ deps.applyTarget === applyOwner &&
2059
+ admission?.prepareLiveInput === prepareInput &&
2060
+ admission?.appendBatch === append &&
2061
+ !!status?.current()));
2062
+ if (retained?.status &&
2063
+ retained.operationId !== captured.operationId &&
2064
+ retained.outcome?.status === "released" &&
2065
+ (retained.status.result !== "completed" ||
2066
+ !retained.status.ports?.inspectCompletion.call(retained.status.plan)))
2067
+ throw new Error("Released held command is unconfirmed; no carrier replacement.");
2068
+ if (heldMarker && !status) {
2069
+ const { recipientInstanceId, recipientRegistrationGeneration, recipientSessionId, recipientBindingKey, operationId, updates, } = captured;
2070
+ const owner = getCommandOwner(structuredClone({
2071
+ recipientInstanceId,
2072
+ recipientRegistrationGeneration,
2073
+ recipientSessionId,
2074
+ recipientBindingKey,
2075
+ operationId,
2076
+ updates,
2077
+ selectedCommand,
2078
+ }), ctx);
2079
+ if (!owner ||
2080
+ typeof owner.isCurrent !== "function" ||
2081
+ typeof owner.prepare !== "function" ||
2082
+ typeof owner.assertRecipientCurrent !== "function")
2083
+ throw new Error("Selected command preparation owner is unavailable.");
2084
+ const ports = {
2085
+ isCurrent: owner.isCurrent,
2086
+ prepare: owner.prepare,
2087
+ assertRecipientCurrent: owner.assertRecipientCurrent,
2088
+ };
2089
+ status = {
2090
+ owner,
2091
+ current: () => Object.entries(ports).every(([key, value]) => Reflect.get(owner, key) === value) &&
2092
+ (!status?.plan ||
2093
+ Object.entries(status.ports).every(([key, value]) => Reflect.get(status.plan, key) === value)) &&
2094
+ ports.isCurrent.call(owner),
2095
+ prepare: ports.prepare,
2096
+ recipient: ports.assertRecipientCurrent,
2097
+ };
2098
+ }
2099
+ if (!admission?.prepareLiveInput || !current())
2100
+ throw new Error("Live-rebind recipient admission is unavailable.");
2101
+ const digest = createHash("sha256")
2102
+ .update(JSON.stringify(captured.updates))
2103
+ .digest("hex");
2104
+ if (retained?.status && !retained.current())
2105
+ throw new Error("Prepared command authority changed; no replacement adoption.");
2106
+ if (retained &&
2107
+ (!retained.current() ||
2108
+ (retained.operationId !== envelope.operationId &&
2109
+ (retained.outcome?.status === "released" ||
2110
+ retained.outcome?.status === "discarded"))))
2111
+ retained = undefined;
2112
+ if (heldMarker &&
2113
+ retained &&
2114
+ (retained.ctx !== ctx ||
2115
+ retained.observation.recipient.instanceId !==
2116
+ captured.recipientInstanceId ||
2117
+ retained.observation.recipient.sessionId !==
2118
+ captured.recipientSessionId ||
2119
+ retained.observation.recipient.generation !==
2120
+ captured.recipientRegistrationGeneration ||
2121
+ retained.observation.recipient.bindingKey !==
2122
+ captured.recipientBindingKey))
2123
+ throw new Error("Selected command saved recipient changed.");
2124
+ if (retained &&
2125
+ (retained.operationId !== captured.operationId ||
2126
+ retained.digest !== digest ||
2127
+ !isDeepStrictEqual(retained.observation.selectedCommand, selectedCommand)))
2128
+ throw new Error("Another live-rebind input or branch is already prepared.");
2129
+ if (!retained) {
2130
+ const held = admission.prepareLiveInput(ctx, captured.updates.map((update) => update.update_id), current);
2131
+ if (!held)
2132
+ throw new Error("Live-rebind recipient input cannot be prepared.");
2133
+ if (heldMarker && !held.canPrepareStatusConsumption?.()) {
2134
+ held.cancelEmpty();
2135
+ throw new Error("Selected command native consumption is unavailable.");
2136
+ }
2137
+ retained = {
2138
+ operationId: captured.operationId,
2139
+ digest,
2140
+ current,
2141
+ held,
2142
+ ctx,
2143
+ ...(status ? { status } : {}),
2144
+ observation: {
2145
+ operationId: captured.operationId,
2146
+ recipient: {
2147
+ instanceId: captured.recipientInstanceId,
2148
+ sessionId: captured.recipientSessionId,
2149
+ generation: captured.recipientRegistrationGeneration,
2150
+ bindingKey: captured.recipientBindingKey,
2151
+ },
2152
+ sourceUpdateIds: [...held.sourceUpdateIds].sort((a, b) => a - b),
2153
+ ...(selectedCommand
2154
+ ? { selectedCommand: structuredClone(selectedCommand) }
2155
+ : {}),
2156
+ status: "saved",
2157
+ },
2158
+ };
2159
+ try {
2160
+ admission.appendBatch(captured.updates);
2161
+ }
2162
+ catch (error) {
2163
+ if (!held.confirmSaved())
2164
+ throw error;
2165
+ }
2166
+ }
2167
+ if (!current() || !retained.held.confirmSaved())
2168
+ throw new Error("Live-rebind recipient save is unconfirmed.");
2169
+ if (status && !status.plan) {
2170
+ if (status.attempted)
2171
+ throw new Error("Selected command preparation is unconfirmed; no replay.");
2172
+ status.attempted = true;
2173
+ const record = retained, readiness = record.held.prepareSourceCompletion?.();
2174
+ const assertSourceCurrent = () => {
2175
+ if (retained !== record ||
2176
+ !record.current() ||
2177
+ !record.held.isOwnerCurrent())
2178
+ throw new Error("Selected command source owner changed.");
2179
+ };
2180
+ const plan = readiness &&
2181
+ status.prepare.call(status.owner, readiness, ctx, {
2182
+ target: { ...heldMarker.target },
2183
+ assertSourceCurrent,
2184
+ assertRecipientCurrent: status.recipient,
2185
+ });
2186
+ if (!plan ||
2187
+ !current() ||
2188
+ !record.held.confirmSaved() ||
2189
+ !isDeepStrictEqual(plan.source, readiness.snapshot.source) ||
2190
+ [plan.bindCarrier, plan.execute, plan.inspectCompletion].some((port) => typeof port !== "function"))
2191
+ throw new Error("Selected command plan is unavailable.");
2192
+ status.plan = plan;
2193
+ status.ports = {
2194
+ bindCarrier: plan.bindCarrier,
2195
+ execute: plan.execute,
2196
+ inspectCompletion: plan.inspectCompletion,
2197
+ };
2198
+ const continuation = {
2199
+ source: plan.source,
2200
+ assertCurrent() {
2201
+ assertSourceCurrent();
2202
+ if (record.outcome?.status !== "released")
2203
+ throw new Error("Selected command has no confirmed peer release.");
2204
+ status.recipient.call(status.owner);
2205
+ },
2206
+ bindCarrier: status.ports.bindCarrier.bind(plan),
2207
+ async execute() {
2208
+ status.result = "unknown";
2209
+ const completed = await status.ports.execute.call(plan);
2210
+ if (completed && record.current() && record.held.isOwnerCurrent())
2211
+ status.result = "completed";
2212
+ return completed;
2213
+ },
2214
+ };
2215
+ if (!record.held.prepareStatusConsumption?.(continuation) ||
2216
+ !current()) {
2217
+ delete status.plan;
2218
+ delete status.ports;
2219
+ throw new Error("Selected command consumption is unconfirmed.");
2220
+ }
2221
+ record.observation.preparedSource = { ...plan.source };
2222
+ }
2223
+ return structuredClone(retained.observation);
2224
+ },
2225
+ async apply(envelope, ctx, isCurrent) {
2226
+ const record = retained;
2227
+ if (envelope.selectedCommand !== undefined && !record?.status?.plan)
2228
+ throw new Error("Selected command native consumption is unavailable.");
2229
+ const current = () => retained === record &&
2230
+ !!record &&
2231
+ record.ctx === ctx &&
2232
+ record.current() &&
2233
+ isCurrent() &&
2234
+ record.held.isCurrent() &&
2235
+ matchesLiveRebindBranch(record.observation, !!record.status, envelope);
2236
+ if (!record ||
2237
+ !current() ||
2238
+ !record.held.confirmSaved() ||
2239
+ !deps.applyTarget ||
2240
+ record.applying ||
2241
+ record.settling ||
2242
+ !matchesLiveRebindCarrier(record.observation, envelope))
2243
+ throw new Error("Live-rebind apply has no exact current saved carrier.");
2244
+ const mode = envelope.mode === "apply" && !record.applyIssued ? "apply" : "inspect";
2245
+ if (mode === "apply")
2246
+ record.applyIssued = true;
2247
+ record.applying = true;
2248
+ try {
2249
+ const observation = await deps.applyTarget({
2250
+ operationId: record.operationId,
2251
+ registrationGeneration: envelope.recipientRegistrationGeneration,
2252
+ mode,
2253
+ liveRebind: {
2254
+ sourceUpdateIds: record.observation.sourceUpdateIds,
2255
+ isCurrent: current,
2256
+ ...copyLiveRebindCommandTarget(record.observation),
2257
+ },
2258
+ }, ctx);
2259
+ if (!current() ||
2260
+ !record.held.confirmSaved() ||
2261
+ observation.operationId !== record.operationId ||
2262
+ observation.recipient.instanceId !== envelope.recipientInstanceId ||
2263
+ observation.recipient.sessionId !== envelope.recipientSessionId ||
2264
+ observation.recipient.generation !==
2265
+ envelope.recipientRegistrationGeneration)
2266
+ throw new Error("Live-rebind apply observation changed.");
2267
+ return {
2268
+ ...structuredClone(record.observation),
2269
+ target: { ...observation.target },
2270
+ slot: observation.slot,
2271
+ status: observation.ready ? "applied" : "saved",
2272
+ };
2273
+ }
2274
+ finally {
2275
+ delete record.applying;
2276
+ }
2277
+ },
2278
+ async settle(envelope, ctx, isCurrent) {
2279
+ if (envelope.mode === "observe-command")
2280
+ return inspectCommandCompletion(envelope, ctx, isCurrent);
2281
+ const record = retained, status = record?.status, plan = status?.plan;
2282
+ if (envelope.selectedCommand !== undefined && !plan)
2283
+ throw new Error("Selected command native consumption is unavailable.");
2284
+ // Only the marked read-only held work sample may outlive the source worker, through the captured recipient owner.
2285
+ const commandWork = envelope.mode === "observe" && envelope.selectedCommand !== undefined;
2286
+ const recipientCurrent = () => {
2287
+ if (record?.status !== status ||
2288
+ status?.plan !== plan ||
2289
+ !status?.current())
2290
+ return false;
2291
+ try {
2292
+ status.recipient.call(status.owner);
2293
+ return true;
2294
+ }
2295
+ catch {
2296
+ return false;
2297
+ }
2298
+ };
2299
+ const current = () => retained === record &&
2300
+ !!record &&
2301
+ record.ctx === ctx &&
2302
+ record.current() &&
2303
+ isCurrent() &&
2304
+ (commandWork ? recipientCurrent() : record.held.isOwnerCurrent()) &&
2305
+ matchesLiveRebindBranch(record.observation, !!record.status, envelope);
2306
+ if (!record ||
2307
+ !current() ||
2308
+ record.applying ||
2309
+ record.settling ||
2310
+ !matchesLiveRebindCarrier(record.observation, envelope))
2311
+ throw new Error("Live-rebind settlement has no exact current carrier.");
2312
+ if (envelope.mode === "observe") {
2313
+ if (record.outcome?.status !== "released" ||
2314
+ !deps.applyTarget ||
2315
+ !deps.observeWork ||
2316
+ !envelope.oldTarget)
2317
+ throw new Error("Live-rebind work observation has no released carrier or observer.");
2318
+ const oldTarget = { ...envelope.oldTarget };
2319
+ let work;
2320
+ record.settling = true;
2321
+ try {
2322
+ await deps.applyTarget({
2323
+ operationId: record.operationId,
2324
+ registrationGeneration: envelope.recipientRegistrationGeneration,
2325
+ mode: "inspect",
2326
+ liveRebind: {
2327
+ sourceUpdateIds: record.observation.sourceUpdateIds,
2328
+ isCurrent: current,
2329
+ ...copyLiveRebindCommandTarget(record.observation),
2330
+ observeWork(canonicalOldTarget) {
2331
+ if (!current() ||
2332
+ !isDeepStrictEqual(canonicalOldTarget, oldTarget))
2333
+ throw new Error("Live-rebind old work target changed.");
2334
+ work = structuredClone(deps.observeWork(canonicalOldTarget, ctx));
2335
+ if (!current() ||
2336
+ !isDeepStrictEqual(canonicalOldTarget, oldTarget))
2337
+ throw new Error("Live-rebind work observer authority or target changed.");
2338
+ },
2339
+ },
2340
+ }, ctx);
2341
+ if (!current() || !work)
2342
+ throw new Error("Live-rebind work observation is unconfirmed.");
2343
+ // Unfinished or unconfirmed selected execution stays unknown; later settled delivery cannot clear it.
2344
+ if (commandWork && status.result !== "completed")
2345
+ work.unknown = true;
2346
+ if (!current())
2347
+ throw new Error("Live-rebind work observer authority changed.");
2348
+ return {
2349
+ ...structuredClone(record.observation),
2350
+ status: "observed",
2351
+ oldTarget,
2352
+ work,
2353
+ };
2354
+ }
2355
+ finally {
2356
+ delete record.settling;
2357
+ }
2358
+ }
2359
+ const action = envelope.mode;
2360
+ if (record.outcome) {
2361
+ if (record.outcome.action !== envelope.mode)
2362
+ throw new Error("Live-rebind input already has another disposition.");
2363
+ return structuredClone(record.outcome);
2364
+ }
2365
+ record.settling = true;
2366
+ const observation = (status) => ({
2367
+ ...structuredClone(record.observation),
2368
+ action,
2369
+ status,
2370
+ });
2371
+ try {
2372
+ if (envelope.mode === "discard") {
2373
+ record.outcome = observation("unknown");
2374
+ const status = record.held.discardSaved(current);
2375
+ record.outcome =
2376
+ status === "protected" ? undefined : observation(status);
2377
+ if (!current())
2378
+ throw new Error("Live-rebind disposal authority changed.");
2379
+ return observation(status);
2380
+ }
2381
+ if (!record.held.isCurrent() ||
2382
+ !record.held.confirmSaved() ||
2383
+ !deps.applyTarget)
2384
+ throw new Error("Live-rebind input cannot be released.");
2385
+ await deps.applyTarget({
2386
+ operationId: record.operationId,
2387
+ registrationGeneration: envelope.recipientRegistrationGeneration,
2388
+ mode: "inspect",
2389
+ liveRebind: {
2390
+ sourceUpdateIds: record.observation.sourceUpdateIds,
2391
+ isCurrent: current,
2392
+ ...copyLiveRebindCommandTarget(record.observation),
2393
+ release(canRelease) {
2394
+ record.outcome = observation("unknown");
2395
+ record.outcome = record.held.release(() => current() && canRelease())
2396
+ ? observation("released")
2397
+ : undefined;
2398
+ },
2399
+ },
2400
+ }, ctx);
2401
+ if (!current())
2402
+ throw new Error("Live-rebind release authority changed.");
2403
+ return structuredClone(record.outcome ?? observation("protected"));
2404
+ }
2405
+ finally {
2406
+ delete record.settling;
2407
+ }
2408
+ },
2409
+ };
2410
+ async function inspectCommandCompletion(envelope, ctx, isCurrent) {
2411
+ const record = retained;
2412
+ const current = () => retained === record &&
2413
+ !!record?.status?.plan &&
2414
+ record.ctx === ctx &&
2415
+ record.current() &&
2416
+ record.held.isOwnerCurrent() &&
2417
+ isCurrent() &&
2418
+ matchesLiveRebindBranch(record.observation, true, envelope);
2419
+ if (!record?.status?.plan ||
2420
+ !deps.applyTarget ||
2421
+ !current() ||
2422
+ record.outcome?.status !== "released" ||
2423
+ record.applying ||
2424
+ record.settling ||
2425
+ !matchesLiveRebindCarrier(record.observation, envelope))
2426
+ throw new Error("Selected command completion has no exact released carrier.");
2427
+ record.settling = true;
2428
+ try {
2429
+ const assertRecipient = () => {
2430
+ record.status.recipient.call(record.status.owner);
2431
+ if (!current())
2432
+ throw new Error("Selected command completion recipient changed.");
2433
+ };
2434
+ assertRecipient();
2435
+ const observed = await deps.applyTarget({
2436
+ operationId: record.operationId,
2437
+ registrationGeneration: record.observation.recipient.generation,
2438
+ mode: "inspect",
2439
+ liveRebind: {
2440
+ sourceUpdateIds: record.observation.sourceUpdateIds,
2441
+ expectedTarget: { ...record.observation.selectedCommand.target },
2442
+ isCurrent: current,
2443
+ },
2444
+ }, ctx);
2445
+ if (!observed.ready || !current())
2446
+ throw new Error("Selected command completion target is unconfirmed.");
2447
+ assertRecipient();
2448
+ const sourceAck = record.status.ports.inspectCompletion.call(record.status.plan);
2449
+ assertRecipient();
2450
+ if (sourceAck &&
2451
+ !isDeepStrictEqual(sourceAck, record.observation.preparedSource))
2452
+ throw new Error("Selected command completion exact ACK changed.");
2453
+ const settled = {
2454
+ preparedSource: { ...record.observation.preparedSource },
2455
+ command: record.status.result ?? "not-issued",
2456
+ ...(sourceAck ? { sourceAck: { ...sourceAck } } : {}),
2457
+ };
2458
+ const { selectedCommand } = record.observation;
2459
+ if (envelope.mode !== "observe-command" || !selectedCommand)
2460
+ throw new Error("Held completion mode does not match its marker.");
2461
+ return {
2462
+ ...structuredClone(record.observation),
2463
+ status: "command-observed",
2464
+ selectedCommand: structuredClone(selectedCommand),
2465
+ ...settled,
2466
+ };
2467
+ }
2468
+ finally {
2469
+ delete record.settling;
2470
+ }
2471
+ }
2472
+ return runtime;
2473
+ }
1608
2474
  export function createTelegramBusFollowerDurableAdmissionRuntime(deps) {
1609
2475
  // The journal forgets a delivery once its worker completes; this window absorbs a leader retry after a lost ACK.
1610
2476
  const recentDeliveries = new Map();
1611
2477
  const getNowMs = deps.getNowMs ?? Date.now;
1612
- const limit = deps.recentDeliveryLimit ?? { maxAgeMs: 24 * 60 * 60 * 1000, maxEntries: 4096 };
2478
+ const limit = deps.recentDeliveryLimit ?? {
2479
+ maxAgeMs: 24 * 60 * 60 * 1000,
2480
+ maxEntries: 4096,
2481
+ };
1613
2482
  const pruneRecentDeliveries = (nowMs) => {
1614
2483
  for (const [deliveryId, admittedAtMs] of recentDeliveries) {
1615
- if (recentDeliveries.size <= limit.maxEntries && nowMs - admittedAtMs < limit.maxAgeMs)
2484
+ if (recentDeliveries.size <= limit.maxEntries &&
2485
+ nowMs - admittedAtMs < limit.maxAgeMs)
1616
2486
  break;
1617
2487
  recentDeliveries.delete(deliveryId);
1618
2488
  }
@@ -1683,12 +2553,17 @@ export function createTelegramBusFollowerInputCustodyPorts(deps) {
1683
2553
  return {
1684
2554
  isSourceReferenceAdmissionEnabled: () => Boolean(deps.getInputCustodyBus()),
1685
2555
  handleInputCustodyHandoff(envelope, ctx) {
1686
- return getRequired().acceptHandoff({ sourceRecoveryKey: envelope.sourceRecoveryKey,
2556
+ return getRequired().acceptHandoff({
2557
+ sourceRecoveryKey: envelope.sourceRecoveryKey,
1687
2558
  recipientBindingKey: envelope.recipientBindingKey,
1688
- source: envelope.source, handoffId: envelope.handoffId }, ctx);
2559
+ source: envelope.source,
2560
+ handoffId: envelope.handoffId,
2561
+ }, ctx);
1689
2562
  },
1690
2563
  sourceReferenceAdmission: createTelegramBusFollowerSourceReferenceAdmissionRuntime({
1691
- wakeSource(input, ctx) { getRequired().wakeSource(input, ctx); },
2564
+ wakeSource(input, ctx) {
2565
+ getRequired().wakeSource(input, ctx);
2566
+ },
1692
2567
  }),
1693
2568
  resolveInputCustodyReference(input) {
1694
2569
  return deps.getInputCustodyBus()?.resolveForwardReference(input);
@@ -1705,23 +2580,35 @@ export function createTelegramBusFollowerSourceReferenceAdmissionRuntime(deps) {
1705
2580
  throw new Error("Telegram follower source-reference admission requires a recovery key.");
1706
2581
  if (!delivery.sourceClaim)
1707
2582
  throw new Error("Telegram follower source-reference admission requires exact claim evidence.");
1708
- const expected = createTelegramBusFollowerDeliveryIdentity({ kind: envelope.kind,
2583
+ const expected = createTelegramBusFollowerDeliveryIdentity({
2584
+ kind: envelope.kind,
1709
2585
  recipientBindingKey: delivery.recipientBindingKey,
1710
- sourceUpdateId: delivery.sourceUpdateId });
2586
+ sourceUpdateId: delivery.sourceUpdateId,
2587
+ });
1711
2588
  if (expected.deliveryId !== delivery.deliveryId)
1712
2589
  throw new Error("Invalid Telegram follower source-reference delivery id.");
1713
- const carrier = envelope.kind === "leader.wakeInputCustody" ? undefined
1714
- : envelope.kind === "leader.forwardCallback" ? envelope.query
1715
- : envelope.kind === "leader.forwardReaction" ? envelope.reactionUpdate : envelope.message;
2590
+ const carrier = envelope.kind === "leader.wakeInputCustody"
2591
+ ? undefined
2592
+ : envelope.kind === "leader.forwardCallback"
2593
+ ? envelope.query
2594
+ : envelope.kind === "leader.forwardReaction"
2595
+ ? envelope.reactionUpdate
2596
+ : envelope.message;
1716
2597
  if (envelope.kind !== "leader.wakeInputCustody" &&
1717
- (!isRecord(carrier) || carrier.pi_telegram_source_update_id !== delivery.sourceUpdateId))
2598
+ (!isRecord(carrier) ||
2599
+ carrier.pi_telegram_source_update_id !== delivery.sourceUpdateId))
1718
2600
  throw new Error("Telegram follower source-reference update id mismatch.");
1719
- await deps.wakeSource({ deliveryId: delivery.deliveryId,
2601
+ await deps.wakeSource({
2602
+ deliveryId: delivery.deliveryId,
1720
2603
  sourceUpdateId: delivery.sourceUpdateId,
1721
2604
  recipientBindingKey: delivery.recipientBindingKey,
1722
2605
  sourceRecoveryKey: delivery.sourceRecoveryKey,
1723
- sourceClaim: { ...delivery.sourceClaim } }, ctx);
1724
- return { deliveryId: delivery.deliveryId, sourceUpdateId: delivery.sourceUpdateId };
2606
+ sourceClaim: { ...delivery.sourceClaim },
2607
+ }, ctx);
2608
+ return {
2609
+ deliveryId: delivery.deliveryId,
2610
+ sourceUpdateId: delivery.sourceUpdateId,
2611
+ };
1725
2612
  },
1726
2613
  };
1727
2614
  }
@@ -1747,71 +2634,153 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
1747
2634
  envelope.kind !== "leader.wakeInputCustody" &&
1748
2635
  envelope.kind !== "leader.offerInputCustodyHandoff" &&
1749
2636
  envelope.kind !== "leader.workspaceRestore" &&
2637
+ envelope.kind !== "leader.prepareLiveRebind" &&
2638
+ envelope.kind !== "leader.applyLiveRebind" &&
2639
+ envelope.kind !== "leader.settleLiveRebind" &&
1750
2640
  envelope.kind !== "leader.offerQueueHandoff") ||
1751
2641
  envelope.recipientInstanceId !== deps.instanceId) {
1752
- return {
1753
- kind: "bus.ack",
1754
- requestId: envelope.requestId,
1755
- ok: false,
1756
- message: "Telegram bus receiver cannot handle this envelope.",
1757
- };
2642
+ return rejectTelegramBusRequest(envelope.requestId, "Telegram bus receiver cannot handle this envelope.");
1758
2643
  }
1759
2644
  const registrationGeneration = deps.getRegistrationGeneration();
1760
2645
  if (!registrationGeneration ||
1761
2646
  envelope.recipientRegistrationGeneration !== registrationGeneration) {
1762
- return {
1763
- kind: "bus.ack",
1764
- requestId: envelope.requestId,
1765
- ok: false,
1766
- message: "Stale Telegram bus follower registration generation.",
1767
- };
2647
+ return rejectTelegramBusRequest(envelope.requestId, "Stale Telegram bus follower registration generation.");
1768
2648
  }
1769
- if (envelope.kind === "leader.workspaceRestore") {
1770
- const ctx = deps.getContext();
1771
- if (!authSecret || !ctx || !deps.isWorkspaceRestoreEnabled?.() || !deps.handleWorkspaceRestore)
2649
+ if (envelope.kind === "leader.prepareLiveRebind" ||
2650
+ envelope.kind === "leader.applyLiveRebind" ||
2651
+ envelope.kind === "leader.settleLiveRebind") {
2652
+ const apply = envelope.kind !== "leader.prepareLiveRebind", settle = envelope.kind === "leader.settleLiveRebind";
2653
+ const commandInput = envelope.selectedCommand, commandEnabled = deps.isLiveRebindCommandSetEnabled;
2654
+ const required = [
2655
+ TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
2656
+ ...(apply ? [TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY] : []),
2657
+ ...(settle ? [TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE] : []),
2658
+ ...(commandInput
2659
+ ? [
2660
+ TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
2661
+ TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
2662
+ TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
2663
+ TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
2664
+ ]
2665
+ : []),
2666
+ ];
2667
+ const ctx = deps.getContext(), sessionId = deps.getSessionId?.(), bindingKey = deps.getLiveRebindJournalBindingKey?.();
2668
+ const protocol = structuredClone(deps.getLeaderProtocol?.()), localProtocol = structuredClone(deps.getLocalProtocol?.());
2669
+ const current = () => !!authSecret &&
2670
+ deps.getAuthSecret?.() === authSecret &&
2671
+ deps.getContext() === ctx &&
2672
+ deps.getRegistrationGeneration() === registrationGeneration &&
2673
+ deps.getSessionId?.() === sessionId &&
2674
+ deps.getLiveRebindJournalBindingKey?.() === bindingKey &&
2675
+ deps.isLiveRebindSaveEnabled?.() === true &&
2676
+ (!apply || deps.isLiveRebindApplyEnabled?.() === true) &&
2677
+ (!settle || deps.isLiveRebindSettleEnabled?.() === true) &&
2678
+ (!commandInput ||
2679
+ (typeof commandEnabled === "function" &&
2680
+ deps.isLiveRebindCommandSetEnabled === commandEnabled &&
2681
+ commandEnabled())) &&
2682
+ isDeepStrictEqual(deps.getLeaderProtocol?.(), protocol) &&
2683
+ isDeepStrictEqual(deps.getLocalProtocol?.(), localProtocol);
2684
+ if (!authSecret ||
2685
+ !ctx ||
2686
+ !bindingKey ||
2687
+ sessionId !== envelope.recipientSessionId ||
2688
+ bindingKey !== envelope.recipientBindingKey ||
2689
+ !required.every((cap) => hasTelegramBusCapability(protocol, cap) &&
2690
+ hasTelegramBusCapability(localProtocol, cap)) ||
2691
+ !localProtocol ||
2692
+ !getTelegramBusProtocolCompatibility({
2693
+ local: localProtocol,
2694
+ remote: protocol,
2695
+ }).compatible ||
2696
+ !current() ||
2697
+ (settle
2698
+ ? !deps.handleLiveRebindSettle
2699
+ : apply
2700
+ ? !deps.handleLiveRebindApply
2701
+ : !deps.handleLiveRebindSave))
2702
+ return rejectTelegramBusRequest(envelope.requestId, "Live-rebind capability or recipient authority is unavailable.");
2703
+ try {
2704
+ const result = envelope.kind === "leader.settleLiveRebind"
2705
+ ? await deps.handleLiveRebindSettle(envelope, ctx, current)
2706
+ : envelope.kind === "leader.applyLiveRebind"
2707
+ ? await deps.handleLiveRebindApply(envelope, ctx, current)
2708
+ : await deps.handleLiveRebindSave(envelope, ctx, current);
2709
+ if (!current())
2710
+ throw new Error("Live-rebind receiver authority changed.");
1772
2711
  return {
1773
- kind: "bus.ack", requestId: envelope.requestId, ok: false, message: "Workspace Restore capability is unavailable.",
2712
+ kind: "bus.ack",
2713
+ requestId: envelope.requestId,
2714
+ ok: true,
2715
+ result,
1774
2716
  };
2717
+ }
2718
+ catch (error) {
2719
+ deps.recordRuntimeEvent?.("bus", error, {
2720
+ phase: "follower-live-rebind-control",
2721
+ });
2722
+ return {
2723
+ kind: "bus.ack",
2724
+ requestId: envelope.requestId,
2725
+ ok: false,
2726
+ message: error instanceof Error
2727
+ ? error.message
2728
+ : "Live-rebind request failed.",
2729
+ };
2730
+ }
2731
+ }
2732
+ if (envelope.kind === "leader.workspaceRestore") {
2733
+ const ctx = deps.getContext();
2734
+ if (!authSecret ||
2735
+ !ctx ||
2736
+ !deps.isWorkspaceRestoreEnabled?.() ||
2737
+ !deps.handleWorkspaceRestore)
2738
+ return rejectTelegramBusRequest(envelope.requestId, "Workspace Restore capability is unavailable.");
1775
2739
  try {
1776
- const result = await deps.handleWorkspaceRestore({ operationId: envelope.operationId,
1777
- registrationGeneration, mode: envelope.mode }, ctx);
1778
- if (deps.getAuthSecret?.() !== authSecret || deps.getRegistrationGeneration() !== registrationGeneration ||
1779
- deps.getContext() !== ctx || !deps.isWorkspaceRestoreEnabled())
2740
+ const result = await deps.handleWorkspaceRestore({
2741
+ operationId: envelope.operationId,
2742
+ registrationGeneration,
2743
+ mode: envelope.mode,
2744
+ }, ctx);
2745
+ if (deps.getAuthSecret?.() !== authSecret ||
2746
+ deps.getRegistrationGeneration() !== registrationGeneration ||
2747
+ deps.getContext() !== ctx ||
2748
+ !deps.isWorkspaceRestoreEnabled())
1780
2749
  throw new Error("Workspace Restore receiver authority changed.");
1781
- return { kind: "bus.ack", requestId: envelope.requestId, ok: true, result };
2750
+ return {
2751
+ kind: "bus.ack",
2752
+ requestId: envelope.requestId,
2753
+ ok: true,
2754
+ result,
2755
+ };
1782
2756
  }
1783
2757
  catch (error) {
1784
- deps.recordRuntimeEvent?.("bus", error, { phase: "follower-workspace-restore" });
1785
- return { kind: "bus.ack", requestId: envelope.requestId, ok: false,
1786
- message: error instanceof Error ? error.message : "Workspace Restore failed." };
2758
+ deps.recordRuntimeEvent?.("bus", error, {
2759
+ phase: "follower-workspace-restore",
2760
+ });
2761
+ return {
2762
+ kind: "bus.ack",
2763
+ requestId: envelope.requestId,
2764
+ ok: false,
2765
+ message: error instanceof Error
2766
+ ? error.message
2767
+ : "Workspace Restore failed.",
2768
+ };
1787
2769
  }
1788
2770
  }
1789
2771
  if (envelope.kind === "leader.offerInputCustodyHandoff" &&
1790
2772
  envelope.recipientBindingKey !== deps.getRecipientBindingKey())
1791
- return {
1792
- kind: "bus.ack", requestId: envelope.requestId, ok: false,
1793
- message: "Mismatched Telegram follower handoff recipient identity.",
1794
- };
2773
+ return rejectTelegramBusRequest(envelope.requestId, "Mismatched Telegram follower handoff recipient identity.");
1795
2774
  if (envelope.kind !== "leader.offerQueueHandoff" &&
1796
2775
  envelope.kind !== "leader.offerInputCustodyHandoff" &&
1797
2776
  (!envelope.delivery ||
1798
2777
  envelope.delivery.recipientBindingKey !==
1799
2778
  deps.getRecipientBindingKey())) {
1800
- return {
1801
- kind: "bus.ack",
1802
- requestId: envelope.requestId,
1803
- ok: false,
1804
- message: "Mismatched Telegram follower delivery identity.",
1805
- };
2779
+ return rejectTelegramBusRequest(envelope.requestId, "Mismatched Telegram follower delivery identity.");
1806
2780
  }
1807
2781
  const ctx = deps.getContext();
1808
2782
  if (!ctx) {
1809
- return {
1810
- kind: "bus.ack",
1811
- requestId: envelope.requestId,
1812
- ok: false,
1813
- message: "Telegram bus follower has no active context.",
1814
- };
2783
+ return rejectTelegramBusRequest(envelope.requestId, "Telegram bus follower has no active context.");
1815
2784
  }
1816
2785
  try {
1817
2786
  if (envelope.kind === "leader.offerInputCustodyHandoff") {
@@ -1822,7 +2791,12 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
1822
2791
  if (!deps.handleInputCustodyHandoff)
1823
2792
  throw new Error("Telegram input custody handoff acceptance is unavailable.");
1824
2793
  const result = await deps.handleInputCustodyHandoff(envelope, ctx);
1825
- return { kind: "bus.ack", requestId: envelope.requestId, ok: true, result };
2794
+ return {
2795
+ kind: "bus.ack",
2796
+ requestId: envelope.requestId,
2797
+ ok: true,
2798
+ result,
2799
+ };
1826
2800
  }
1827
2801
  if (envelope.kind !== "leader.offerQueueHandoff") {
1828
2802
  const sourceReferenceEnabled = envelope.kind === "leader.wakeInputCustody" ||
@@ -1831,7 +2805,8 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
1831
2805
  !deps.hasAuthenticatedSourceReferenceTransport?.())
1832
2806
  throw new Error("Telegram follower source-reference admission requires authenticated transport.");
1833
2807
  const admission = sourceReferenceEnabled
1834
- ? deps.sourceReferenceAdmission : deps.durableAdmission;
2808
+ ? deps.sourceReferenceAdmission
2809
+ : deps.durableAdmission;
1835
2810
  if (!admission)
1836
2811
  throw new Error("Telegram follower source-reference admission is enabled without a wake authority.");
1837
2812
  const receipt = await admission.admit(envelope, ctx);
@@ -1892,13 +2867,3 @@ function parseRegistrationResult(value) {
1892
2867
  : {}),
1893
2868
  };
1894
2869
  }
1895
- function parseTarget(value) {
1896
- if (value === undefined)
1897
- return undefined;
1898
- if (!isRecord(value) || typeof value.chatId !== "number")
1899
- return undefined;
1900
- const target = { chatId: value.chatId };
1901
- if (typeof value.threadId === "number")
1902
- target.threadId = value.threadId;
1903
- return target;
1904
- }