@llblab/pi-telegram 0.52.2 → 0.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +15 -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 +65 -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 +252 -127
  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 +128 -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 +516 -301
  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
@@ -3,8 +3,8 @@
3
3
  * Zones: multi-instance bus, telegram api transport, live instance routing
4
4
  * Wraps the direct Telegram Bot API runtime so follower instances can route outbound calls through the bus leader
5
5
  */
6
- import { markTelegramBusCrossTargetDelivery, stripTelegramBusApiMetadata, } from "./bus.js";
7
- import { buildTelegramAnswerGuestQueryBody, formatTelegramCallbackAnswerText, isTelegramMessageNotModifiedError, } from "./telegram-api.js";
6
+ import { markTelegramBusCrossTargetDelivery, parseTelegramBusSelectedMenuTextEffect, stripTelegramBusApiMetadata, } from "./bus.js";
7
+ import { buildTelegramAnswerGuestQueryBody, isTelegramApiCommitUnknownError, isTelegramMessageNotModifiedError, TelegramApiAuthorityError, TelegramApiCommitUnknownError, } from "./telegram-api.js";
8
8
  function asRecord(value) {
9
9
  return value && typeof value === "object" && !Array.isArray(value)
10
10
  ? value
@@ -37,28 +37,147 @@ function markFollowerCrossTargetDelivery(body, defaultTarget) {
37
37
  function rejectTelegramDirectOwnership(method) {
38
38
  return Promise.reject(new Error(`Telegram ${method} requires direct transport ownership.`));
39
39
  }
40
+ /** Bound selected-menu projection only; no ordinary/raw API fallback and no activation during preparation. */
41
+ export function createTelegramSelectedMenuTextApi(deps) {
42
+ const { operationId, registrationGeneration, assertAuthority, deliver } = deps, target = { ...deps.target };
43
+ if (typeof assertAuthority !== "function" ||
44
+ typeof deliver !== "function" ||
45
+ !operationId?.trim() ||
46
+ operationId.length > 128 ||
47
+ !registrationGeneration?.trim() ||
48
+ registrationGeneration.length > 128 ||
49
+ !Number.isSafeInteger(target.chatId) ||
50
+ target.chatId <= 0 ||
51
+ !Number.isSafeInteger(target.threadId) ||
52
+ target.threadId <= 0)
53
+ throw new TelegramApiAuthorityError(false);
54
+ const invoke = async (edit, raw, options) => {
55
+ let issued = false;
56
+ const assertCurrent = () => {
57
+ try {
58
+ assertAuthority();
59
+ }
60
+ catch {
61
+ throw new TelegramApiAuthorityError(issued);
62
+ }
63
+ };
64
+ try {
65
+ if (options?.assertAuthority !== assertAuthority ||
66
+ Reflect.ownKeys(raw).some((key) => typeof key !== "string" ||
67
+ ![
68
+ "chat_id",
69
+ "message_thread_id",
70
+ "text",
71
+ "parse_mode",
72
+ "reply_markup",
73
+ ...(edit ? ["message_id"] : ["reply_parameters"]),
74
+ ].includes(key)))
75
+ throw new TelegramApiAuthorityError(false);
76
+ const body = structuredClone(raw), reply = body.reply_parameters;
77
+ if (body.chat_id !== target.chatId ||
78
+ body.message_thread_id !== target.threadId ||
79
+ (reply !== undefined &&
80
+ (!reply ||
81
+ typeof reply !== "object" ||
82
+ Array.isArray(reply) ||
83
+ Object.keys(reply).some((key) => !["message_id", "allow_sending_without_reply"].includes(key)) ||
84
+ reply.allow_sending_without_reply !==
85
+ true ||
86
+ !Number.isSafeInteger(reply.message_id) ||
87
+ reply.message_id <= 0)))
88
+ throw new TelegramApiAuthorityError(false);
89
+ const effect = parseTelegramBusSelectedMenuTextEffect({
90
+ kind: edit ? "edit-text" : "send-text",
91
+ text: body.text,
92
+ ...(body.parse_mode !== undefined
93
+ ? { parseMode: body.parse_mode }
94
+ : {}),
95
+ ...(body.reply_markup !== undefined
96
+ ? { replyMarkup: body.reply_markup }
97
+ : {}),
98
+ ...(edit
99
+ ? { messageId: body.message_id }
100
+ : reply !== undefined
101
+ ? {
102
+ replyToMessageId: reply.message_id,
103
+ }
104
+ : {}),
105
+ });
106
+ if (!effect)
107
+ throw new TelegramApiAuthorityError(false);
108
+ assertCurrent();
109
+ issued = true;
110
+ const observed = await deliver.call(deps, effect, assertAuthority);
111
+ assertCurrent();
112
+ if (observed.operationId !== operationId ||
113
+ observed.registrationGeneration !== registrationGeneration ||
114
+ observed.effect !== effect.kind ||
115
+ observed.recipient.target.chatId !== target.chatId ||
116
+ observed.recipient.target.threadId !== target.threadId ||
117
+ !Number.isSafeInteger(observed.messageId) ||
118
+ observed.messageId <= 0 ||
119
+ (effect.kind === "edit-text" && observed.messageId !== effect.messageId))
120
+ throw new Error("Selected-menu transport result is unconfirmed.");
121
+ assertCurrent();
122
+ return observed.messageId;
123
+ }
124
+ catch (error) {
125
+ if (error instanceof TelegramApiAuthorityError ||
126
+ isTelegramApiCommitUnknownError(error))
127
+ throw error;
128
+ if (issued)
129
+ throw new TelegramApiCommitUnknownError(edit ? "editMessageText" : "sendMessage", error);
130
+ throw new TelegramApiAuthorityError(false);
131
+ }
132
+ };
133
+ return {
134
+ sendMessage: async (body, options) => ({
135
+ message_id: await invoke(false, body, options),
136
+ }),
137
+ editMessageText: async (body, options) => {
138
+ await invoke(true, body, options);
139
+ return "edited";
140
+ },
141
+ };
142
+ }
143
+ /** Followers route every chat action through the leader's generic `sendChatAction` call. */
144
+ function sendTelegramFollowerChatAction(deps, chatId, action, options) {
145
+ const body = withDefaultThreadTarget({
146
+ chat_id: chatId,
147
+ action,
148
+ ...(options?.message_thread_id !== undefined
149
+ ? { message_thread_id: options.message_thread_id }
150
+ : {}),
151
+ }, deps.getDefaultTarget?.());
152
+ return deps.callFollowerApi("call", ["sendChatAction", body]).then(asBoolean);
153
+ }
40
154
  export function createTelegramBusAwareApiRuntime(deps) {
41
155
  return {
42
156
  call(method, body, options) {
43
- return deps.ownsDirect()
44
- ? deps.directRuntime.call(method, body, options)
45
- : deps.callFollowerApi("call", [
46
- method,
47
- body,
48
- options,
49
- ]);
157
+ if (deps.ownsDirect())
158
+ return deps.directRuntime.call(method, body, options);
159
+ // A process-local authority callback cannot cross IPC as an enforceable grant.
160
+ if (options?.assertAuthority)
161
+ return Promise.reject(new TelegramApiAuthorityError(false));
162
+ return deps.callFollowerApi("call", [
163
+ method,
164
+ body,
165
+ options,
166
+ ]);
50
167
  },
51
168
  callMultipart(method, fields, fileField, filePath, fileName, options) {
52
- return deps.ownsDirect()
53
- ? deps.directRuntime.callMultipart(method, fields, fileField, filePath, fileName, options)
54
- : deps.callFollowerApi("callMultipart", [
55
- method,
56
- fields,
57
- fileField,
58
- filePath,
59
- fileName,
60
- options,
61
- ]);
169
+ if (deps.ownsDirect())
170
+ return deps.directRuntime.callMultipart(method, fields, fileField, filePath, fileName, options);
171
+ if (options?.assertAuthority)
172
+ return Promise.reject(new TelegramApiAuthorityError(false));
173
+ return deps.callFollowerApi("callMultipart", [
174
+ method,
175
+ fields,
176
+ fileField,
177
+ filePath,
178
+ fileName,
179
+ options,
180
+ ]);
62
181
  },
63
182
  downloadFile(fileId, suggestedName, source) {
64
183
  // The source names the file (kind-scope-message); dropping it falls back to the bare generated name.
@@ -80,54 +199,29 @@ export function createTelegramBusAwareApiRuntime(deps) {
80
199
  ? deps.directRuntime.getUpdates(body, signal)
81
200
  : rejectTelegramDirectOwnership("getUpdates");
82
201
  },
83
- setMyCommands(commands) {
84
- return deps.ownsDirect()
85
- ? deps.directRuntime.setMyCommands(commands)
86
- : deps
87
- .callFollowerApi("call", ["setMyCommands", { commands }])
88
- .then(asBoolean);
202
+ setMyCommands(commands, options) {
203
+ if (deps.ownsDirect())
204
+ return deps.directRuntime.setMyCommands(commands, options);
205
+ if (options?.assertAuthority)
206
+ return Promise.reject(new TelegramApiAuthorityError(false));
207
+ return deps
208
+ .callFollowerApi("call", ["setMyCommands", { commands }])
209
+ .then(asBoolean);
89
210
  },
90
211
  sendChatAction(chatId, action, options) {
91
- const body = withDefaultThreadTarget({
92
- chat_id: chatId,
93
- action,
94
- ...(options?.message_thread_id !== undefined
95
- ? { message_thread_id: options.message_thread_id }
96
- : {}),
97
- }, deps.getDefaultTarget?.());
98
212
  return deps.ownsDirect()
99
213
  ? deps.directRuntime.sendChatAction(chatId, action, options)
100
- : deps
101
- .callFollowerApi("call", ["sendChatAction", body])
102
- .then(asBoolean);
214
+ : sendTelegramFollowerChatAction(deps, chatId, action, options);
103
215
  },
104
216
  sendTypingAction(chatId, options) {
105
- const body = withDefaultThreadTarget({
106
- chat_id: chatId,
107
- action: "typing",
108
- ...(options?.message_thread_id !== undefined
109
- ? { message_thread_id: options.message_thread_id }
110
- : {}),
111
- }, deps.getDefaultTarget?.());
112
217
  return deps.ownsDirect()
113
218
  ? deps.directRuntime.sendTypingAction(chatId, options)
114
- : deps
115
- .callFollowerApi("call", ["sendChatAction", body])
116
- .then(asBoolean);
219
+ : sendTelegramFollowerChatAction(deps, chatId, "typing", options);
117
220
  },
118
221
  sendRecordVoiceAction(chatId, options) {
119
- const body = withDefaultThreadTarget({
120
- chat_id: chatId,
121
- action: "record_voice",
122
- ...(options?.message_thread_id !== undefined
123
- ? { message_thread_id: options.message_thread_id }
124
- : {}),
125
- }, deps.getDefaultTarget?.());
126
222
  return deps.ownsDirect()
127
223
  ? deps.directRuntime.sendRecordVoiceAction(chatId, options)
128
- : deps
129
- .callFollowerApi("call", ["sendChatAction", body])
130
- .then(asBoolean);
224
+ : sendTelegramFollowerChatAction(deps, chatId, "record_voice", options);
131
225
  },
132
226
  sendMessageDraft(chatId, draftId, text, options) {
133
227
  const body = {
@@ -150,25 +244,29 @@ export function createTelegramBusAwareApiRuntime(deps) {
150
244
  .callFollowerApi("call", ["sendMessageDraft", scopedBody])
151
245
  .then(asBoolean);
152
246
  },
153
- sendMessage(body) {
154
- return deps.ownsDirect()
155
- ? deps.directRuntime.sendMessage(stripTelegramBusApiMetadata(body))
156
- : deps
157
- .callFollowerApi("call", [
158
- "sendMessage",
159
- markFollowerCrossTargetDelivery(body, deps.getDefaultTarget?.()),
160
- ])
161
- .then(asSentMessage);
247
+ sendMessage(body, options) {
248
+ if (deps.ownsDirect())
249
+ return deps.directRuntime.sendMessage(stripTelegramBusApiMetadata(body), options);
250
+ if (options?.assertAuthority)
251
+ return Promise.reject(new TelegramApiAuthorityError(false));
252
+ return deps
253
+ .callFollowerApi("call", [
254
+ "sendMessage",
255
+ markFollowerCrossTargetDelivery(body, deps.getDefaultTarget?.()),
256
+ ])
257
+ .then(asSentMessage);
162
258
  },
163
- sendRichMessage(body) {
164
- return deps.ownsDirect()
165
- ? deps.directRuntime.sendRichMessage(stripTelegramBusApiMetadata(body))
166
- : deps
167
- .callFollowerApi("call", [
168
- "sendRichMessage",
169
- markFollowerCrossTargetDelivery(body, deps.getDefaultTarget?.()),
170
- ])
171
- .then(asSentMessage);
259
+ sendRichMessage(body, options) {
260
+ if (deps.ownsDirect())
261
+ return deps.directRuntime.sendRichMessage(stripTelegramBusApiMetadata(body), options);
262
+ if (options?.assertAuthority)
263
+ return Promise.reject(new TelegramApiAuthorityError(false));
264
+ return deps
265
+ .callFollowerApi("call", [
266
+ "sendRichMessage",
267
+ markFollowerCrossTargetDelivery(body, deps.getDefaultTarget?.()),
268
+ ])
269
+ .then(asSentMessage);
172
270
  },
173
271
  sendRichMessageDraft(body) {
174
272
  return deps.ownsDirect()
@@ -177,9 +275,11 @@ export function createTelegramBusAwareApiRuntime(deps) {
177
275
  .callFollowerApi("call", ["sendRichMessageDraft", body])
178
276
  .then(asBoolean);
179
277
  },
180
- async editMessageText(body) {
278
+ async editMessageText(body, options) {
181
279
  if (deps.ownsDirect())
182
- return deps.directRuntime.editMessageText(body);
280
+ return deps.directRuntime.editMessageText(body, options);
281
+ if (options?.assertAuthority)
282
+ throw new TelegramApiAuthorityError(false);
183
283
  try {
184
284
  await deps.callFollowerApi("call", ["editMessageText", body]);
185
285
  return "edited";
@@ -200,17 +300,20 @@ export function createTelegramBusAwareApiRuntime(deps) {
200
300
  { chat_id: chatId, message_id: messageId, reply_markup: replyMarkup },
201
301
  ]);
202
302
  },
203
- async answerCallbackQuery(callbackQueryId, text) {
204
- const answer = formatTelegramCallbackAnswerText(text);
303
+ async answerCallbackQuery(callbackQueryId, text, options) {
304
+ const assertAuthority = options?.assertAuthority;
205
305
  if (deps.ownsDirect()) {
206
- await deps.directRuntime.answerCallbackQuery(callbackQueryId, answer);
306
+ const authorityOptions = assertAuthority ? [{ assertAuthority }] : [];
307
+ await deps.directRuntime.answerCallbackQuery(callbackQueryId, text, ...authorityOptions);
207
308
  return;
208
309
  }
310
+ if (assertAuthority)
311
+ throw new TelegramApiAuthorityError(false);
209
312
  await deps.callFollowerApi("call", [
210
313
  "answerCallbackQuery",
211
314
  {
212
315
  callback_query_id: callbackQueryId,
213
- ...(answer !== undefined ? { text: answer } : {}),
316
+ ...(text !== undefined ? { text } : {}),
214
317
  },
215
318
  ]);
216
319
  },
@@ -5,14 +5,16 @@
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 type { TelegramPreparedHeldCommand } from "./commands.ts";
8
9
  import type { TelegramWorkspaceRestoreExecutor, TelegramWorkspaceRestoreIntent } from "./threads.ts";
9
- import * as Threads from "./threads.ts";
10
+ import type { TelegramLiveSourceCompletionReadiness, TelegramUpdateAdmissionLifecycleRuntime } from "./updates.ts";
11
+ import { TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, type TelegramBusAgentMessage, type TelegramBusAgentTargetSelector, type TelegramBusEnvelope, type TelegramBusFollowerQueueHandoffOffer, type TelegramBusForwardOwnership, type TelegramBusLiveRebindApplyObservation, type TelegramBusLiveRebindCommandObservation, type TelegramBusLiveRebindSaveObservation, type TelegramBusLiveRebindSettlementObservation, type TelegramBusLiveRebindWorkObservation, type TelegramBusLiveRebindWorkState, type TelegramBusProtocolIdentity, type TelegramBusSelectedMenuDeliveryObservation, type TelegramBusSelectedMenuTextEffect, type TelegramBusSocketPathSource, type TelegramBusWorkspaceRestoreObservation } from "./bus.ts";
12
+ import type { TelegramConfigStore, TelegramThreadDisplayMode } from "./config.ts";
10
13
  import { type TelegramUpdateJournalStoreOptions } from "./journal.ts";
11
14
  import { type TelegramLockEntry, type TelegramLockState } from "./locks.ts";
12
- import type { TelegramQueueHandoffPayload, TelegramQueueHandoffStageResult } from "./queue.ts";
13
- import type { TelegramTarget } from "./target.ts";
14
- import { type TelegramBusAgentMessage, type TelegramBusAgentTargetSelector, type TelegramBusEnvelope, type TelegramBusWorkspaceRestoreObservation, type TelegramBusForwardOwnership, type TelegramBusProtocolIdentity, type TelegramBusSocketPathSource } from "./bus.ts";
15
- import type { TelegramConfigStore, TelegramThreadDisplayMode } from "./config.ts";
15
+ import type { TelegramQueueHandoffStageResult } from "./queue.ts";
16
+ import { type TelegramTarget } from "./target.ts";
17
+ import * as Threads from "./threads.ts";
16
18
  import { type TelegramWorkspaceAdmissionLedger } from "./workspace-admission.ts";
17
19
  export declare const TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS = 8000;
18
20
  export interface TelegramFollowerSessionHandoff {
@@ -233,6 +235,8 @@ export interface TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext> {
233
235
  getAuthSecret?: () => string | undefined;
234
236
  getRegistrationGeneration: () => string | undefined;
235
237
  getRecipientBindingKey: () => string | undefined;
238
+ /** Live preparation names the current journal, not the stable forwarding/profile identity. */
239
+ getLiveRebindJournalBindingKey?: () => string | undefined;
236
240
  durableAdmission: TelegramBusFollowerDurableAdmissionPort<TContext>;
237
241
  sourceReferenceAdmission?: TelegramBusFollowerDurableAdmissionPort<TContext>;
238
242
  isSourceReferenceAdmissionEnabled?: () => boolean;
@@ -244,6 +248,23 @@ export interface TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext> {
244
248
  handleQueueHandoff?: (envelope: Extract<TelegramBusEnvelope, {
245
249
  kind: "leader.offerQueueHandoff";
246
250
  }>, ctx: TContext) => Promise<TelegramQueueHandoffStageResult> | TelegramQueueHandoffStageResult;
251
+ getSessionId?: () => string | undefined;
252
+ getLeaderProtocol?: () => TelegramBusProtocolIdentity | undefined;
253
+ getLocalProtocol?: () => TelegramBusProtocolIdentity | undefined;
254
+ isLiveRebindSaveEnabled?: () => boolean;
255
+ isLiveRebindApplyEnabled?: () => boolean;
256
+ isLiveRebindSettleEnabled?: () => boolean;
257
+ handleLiveRebindSettle?: (envelope: Extract<TelegramBusEnvelope, {
258
+ kind: "leader.settleLiveRebind";
259
+ }>, ctx: TContext, isCurrent: () => boolean) => Promise<TelegramBusLiveRebindSettlementObservation | TelegramBusLiveRebindWorkObservation | TelegramBusLiveRebindCommandObservation>;
260
+ handleLiveRebindApply?: (envelope: Extract<TelegramBusEnvelope, {
261
+ kind: "leader.applyLiveRebind";
262
+ }>, ctx: TContext, isCurrent: () => boolean) => Promise<TelegramBusLiveRebindApplyObservation>;
263
+ /** Availability only; never future recipient activation or a command execution grant. */
264
+ isLiveRebindCommandSetEnabled?: () => boolean;
265
+ handleLiveRebindSave?: (envelope: Extract<TelegramBusEnvelope, {
266
+ kind: "leader.prepareLiveRebind";
267
+ }>, ctx: TContext, isCurrent: () => boolean) => TelegramBusLiveRebindSaveObservation | Promise<TelegramBusLiveRebindSaveObservation>;
247
268
  isWorkspaceRestoreEnabled?: () => boolean;
248
269
  handleWorkspaceRestore?: (input: {
249
270
  operationId: string;
@@ -292,22 +313,47 @@ export declare function createTelegramBusFollowerRestoreContextGetter<TContext>(
292
313
  getLeaderState: () => TelegramLockState;
293
314
  getAuthenticatedSecret: () => string | undefined;
294
315
  getLeaderProtocol: () => TelegramBusProtocolIdentity | undefined;
316
+ capability?: typeof TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE | typeof TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY | typeof TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY;
295
317
  }): (ctx: TContext) => TelegramBusFollowerRestoreContext | undefined;
296
- /** Prepared receiver: its caller authenticates the envelope; no canonical writes, dispatch or cleanup. */
318
+ /** Shared canonical/local target owner and scoped live-carrier hooks; caller authenticates transport, no canonical writes or cleanup. */
297
319
  export declare function createTelegramBusFollowerWorkspaceRestoreHandler<TContext>(deps: {
298
320
  instanceId: string;
299
321
  /** Captures actual Pi lifetime and authenticated leader/profile authority, never request-derived values. */
300
322
  getContextAuthority: (ctx: TContext) => TelegramBusFollowerRestoreContext | undefined;
301
323
  readRestoreIntent: (operationId: string, profileBindingKey: string) => TelegramWorkspaceRestoreIntent | undefined;
302
- topicTargetStore: Pick<Threads.TelegramTopicTargetStore, "load" | "withWorkspaceRestoreSnapshot">;
324
+ readLiveRebindIntent?: (operationId: string, profileBindingKey: string) => Threads.TelegramWorkspaceLiveRebindIntent | undefined;
325
+ topicTargetStore: Pick<Threads.TelegramTopicTargetStore, "load" | "withWorkspaceRestoreSnapshot"> & Partial<Pick<Threads.TelegramTopicTargetStore, "withWorkspaceLiveRebindSnapshot">>;
303
326
  registrationState: Pick<TelegramBusFollowerRegistrationState, "getTarget" | "getSlot" | "getGeneration" | "setRegistered" | "getLeaderProtocol">;
304
327
  getWorkspaceAdmission: NonNullable<TelegramBusFollowerWorkspaceAdmissionDeps["getWorkspaceAdmission"]>;
305
328
  recordRuntimeEvent?: TelegramBusFollowerWorkspaceAdmissionDeps["recordRuntimeEvent"];
306
- }): (input: {
329
+ }): ((input: {
307
330
  operationId: string;
308
331
  registrationGeneration: string;
309
332
  mode: "apply" | "inspect";
310
- }, ctx: TContext) => Promise<TelegramBusWorkspaceRestoreObservation>;
333
+ liveRebind?: {
334
+ sourceUpdateIds: readonly number[];
335
+ isCurrent(): boolean;
336
+ /** Captured prepared-branch target; equality must precede local apply or release grants. */
337
+ expectedTarget?: TelegramTarget & {
338
+ threadId: number;
339
+ };
340
+ release?: (canRelease: () => boolean) => void;
341
+ observeWork?: (oldTarget: TelegramTarget & {
342
+ threadId: number;
343
+ }) => void;
344
+ };
345
+ }, ctx: TContext) => Promise<TelegramBusWorkspaceRestoreObservation>) & {
346
+ /** Capture availability only; asserting current recipient requires canonical released/local-applied ownership. */
347
+ prepareLiveCommandRecipient(input: {
348
+ operationId: string;
349
+ registrationGeneration: string;
350
+ sessionId: string;
351
+ sourceUpdateIds: readonly number[];
352
+ target: TelegramTarget & {
353
+ threadId: number;
354
+ };
355
+ }, ctx: TContext): Pick<TelegramBusFollowerCommandOwner<TContext>, "isCurrent" | "assertRecipientCurrent"> | undefined;
356
+ };
311
357
  export declare function createTelegramBusFollowerClientRuntime<TContext, TReactionUpdate, TCallbackQuery, TMessage = unknown>(deps: TelegramBusFollowerClientRuntimeDeps<TMessage>): {
312
358
  createRequestId: () => string;
313
359
  callApi: (method: string, args: unknown[]) => Promise<unknown>;
@@ -339,29 +385,29 @@ export declare function createTelegramBusFollowerClientRuntime<TContext, TReacti
339
385
  ctx: TContext;
340
386
  }) => Promise<import("./bus.ts").TelegramBusForeignUpdateSettlement>;
341
387
  };
342
- queueHandoff: (input: {
343
- recipientInstanceId: string;
344
- recipientRegistrationGeneration: string;
345
- donorProcessId: number;
346
- donorProcessBirthId: string;
347
- donorSessionGeneration: number;
348
- donorAcquisitionId: string;
349
- donorAcquiredAtMs: number;
350
- handoffToken: string;
351
- payload: TelegramQueueHandoffPayload;
352
- }) => Promise<TelegramQueueHandoffStageResult>;
388
+ queueHandoff: (input: TelegramBusFollowerQueueHandoffOffer) => Promise<TelegramQueueHandoffStageResult>;
353
389
  };
354
- export declare function createTelegramBusFollowerQueueHandoffClient(deps: TelegramBusFollowerApiCallerDeps): (input: {
355
- recipientInstanceId: string;
356
- recipientRegistrationGeneration: string;
357
- donorProcessId: number;
358
- donorProcessBirthId: string;
359
- donorSessionGeneration: number;
360
- donorAcquisitionId: string;
361
- donorAcquiredAtMs: number;
362
- handoffToken: string;
363
- payload: TelegramQueueHandoffPayload;
364
- }) => Promise<TelegramQueueHandoffStageResult>;
390
+ export declare function createTelegramBusFollowerQueueHandoffClient(deps: TelegramBusFollowerApiCallerDeps): (input: TelegramBusFollowerQueueHandoffOffer) => Promise<TelegramQueueHandoffStageResult>;
391
+ /** Private selected text-effect caller; not a guarded ordinary API adapter or follower command admission grant. */
392
+ export declare function createTelegramBusFollowerSelectedMenuCaller<TContext>(deps: {
393
+ client: TelegramBusFollowerApiCallerDeps;
394
+ protocolIdentity: TelegramBusProtocolIdentity;
395
+ recipient: {
396
+ getContextAuthority(ctx: TContext): TelegramBusFollowerRestoreContext | undefined;
397
+ getJournalBindingKey(ctx: TContext): string | undefined;
398
+ getProcessIdentity(): {
399
+ processId: number;
400
+ processBirthId: string;
401
+ };
402
+ registrationState: Pick<TelegramBusFollowerRegistrationState, "getTarget" | "getSlot" | "getGeneration" | "getLeaderProtocol">;
403
+ };
404
+ }): (input: {
405
+ ctx: TContext;
406
+ operationId: string;
407
+ registrationGeneration: string;
408
+ effect: TelegramBusSelectedMenuTextEffect;
409
+ assertAuthority: () => void;
410
+ }) => Promise<TelegramBusSelectedMenuDeliveryObservation>;
365
411
  export declare function createTelegramBusFollowerApiCaller(deps: TelegramBusFollowerApiCallerDeps): (method: string, args: unknown[]) => Promise<unknown>;
366
412
  export declare function createTelegramBusFollowerSessionReplacementSuspender(deps: TelegramBusFollowerSessionReplacementSuspenderDeps): (preserveTarget?: boolean) => Promise<void>;
367
413
  export declare function createTelegramBusFollowerSessionRefreshHook<TContext>(deps: TelegramBusFollowerSessionRefreshHookDeps<TContext>): (_event: unknown, ctx: TContext) => Promise<void>;
@@ -383,6 +429,40 @@ export declare function createTelegramBusFollowerPairedAdmission(deps: {
383
429
  export declare function prepareTelegramBusFollowerJournaledUpdateForExecution<TUpdate extends {
384
430
  message?: unknown;
385
431
  } & Record<string, unknown>>(update: TUpdate, prepareForwardedMessage: (message: NonNullable<TUpdate["message"]>, position: "comment" | "forward") => void): TUpdate;
432
+ /** Existing Commands/menu assembly preflights the copied input and captures independent recipient/transport owners. */
433
+ export interface TelegramBusFollowerCommandOwner<TContext> {
434
+ isCurrent(): boolean;
435
+ assertRecipientCurrent(): void;
436
+ prepare(readiness: TelegramLiveSourceCompletionReadiness, ctx: TContext, input: {
437
+ target: TelegramTarget & {
438
+ threadId: number;
439
+ };
440
+ assertSourceCurrent(): void;
441
+ assertRecipientCurrent(): void;
442
+ }): TelegramPreparedHeldCommand | undefined;
443
+ }
444
+ /** One current warm preparation, not a prompt queue or restart recovery record. */
445
+ export declare function createTelegramBusFollowerLiveRebindRuntime<TContext>(deps: {
446
+ getAdmission(ctx: TContext): Pick<TelegramUpdateAdmissionLifecycleRuntime<TContext>, "prepareLiveInput" | "appendBatch" | "getJournalBindingKey"> | undefined;
447
+ applyTarget?: (...args: Parameters<ReturnType<typeof createTelegramBusFollowerWorkspaceRestoreHandler<TContext>>>) => ReturnType<ReturnType<typeof createTelegramBusFollowerWorkspaceRestoreHandler<TContext>>>;
448
+ observeWork?: (oldTarget: TelegramTarget & {
449
+ threadId: number;
450
+ }, ctx: TContext) => TelegramBusLiveRebindWorkState;
451
+ /** Pure preflight: undefined refuses before hold/append; this port never activates future recipient authority. */
452
+ getCommandOwner?: (input: Readonly<Omit<Extract<TelegramBusEnvelope, {
453
+ kind: "leader.prepareLiveRebind";
454
+ }>, "kind" | "auth" | "requestId" | "sentAtMs">>, ctx: TContext) => TelegramBusFollowerCommandOwner<TContext> | undefined;
455
+ }): {
456
+ save(envelope: Extract<TelegramBusEnvelope, {
457
+ kind: "leader.prepareLiveRebind";
458
+ }>, ctx: TContext, isCurrent: () => boolean): TelegramBusLiveRebindSaveObservation;
459
+ apply(envelope: Extract<TelegramBusEnvelope, {
460
+ kind: "leader.applyLiveRebind";
461
+ }>, ctx: TContext, isCurrent: () => boolean): Promise<TelegramBusLiveRebindApplyObservation>;
462
+ settle(envelope: Extract<TelegramBusEnvelope, {
463
+ kind: "leader.settleLiveRebind";
464
+ }>, ctx: TContext, isCurrent: () => boolean): Promise<TelegramBusLiveRebindSettlementObservation | TelegramBusLiveRebindWorkObservation | TelegramBusLiveRebindCommandObservation>;
465
+ };
386
466
  export declare function createTelegramBusFollowerDurableAdmissionRuntime<TContext>(deps: {
387
467
  journal: {
388
468
  appendBatch(updates: readonly ({