@llblab/pi-telegram 0.46.0 → 0.48.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 +4 -2
  2. package/BACKLOG.md +0 -6
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +5 -2
  5. package/dist/api/activity.d.ts +6 -0
  6. package/dist/api/activity.js +6 -0
  7. package/dist/api/commands.d.ts +6 -0
  8. package/dist/api/commands.js +6 -0
  9. package/dist/api/delivery.d.ts +6 -0
  10. package/dist/api/delivery.js +6 -0
  11. package/dist/api/inbound.d.ts +6 -0
  12. package/dist/api/inbound.js +6 -0
  13. package/dist/api/keyboard.d.ts +6 -0
  14. package/dist/api/keyboard.js +6 -0
  15. package/dist/api/outbound.d.ts +6 -0
  16. package/dist/api/outbound.js +6 -0
  17. package/dist/api/sections.d.ts +7 -0
  18. package/dist/api/sections.js +6 -0
  19. package/dist/api/status.d.ts +6 -0
  20. package/dist/api/status.js +6 -0
  21. package/dist/api/updates.d.ts +6 -0
  22. package/dist/api/updates.js +6 -0
  23. package/dist/api/voice.d.ts +6 -0
  24. package/dist/api/voice.js +6 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +6 -0
  27. package/dist/lib/activity-verbosity.d.ts +52 -0
  28. package/dist/lib/activity-verbosity.js +596 -0
  29. package/dist/lib/activity.d.ts +220 -0
  30. package/dist/lib/activity.js +574 -0
  31. package/dist/lib/agent-messages.d.ts +28 -0
  32. package/dist/lib/agent-messages.js +86 -0
  33. package/dist/lib/bindings.d.ts +250 -0
  34. package/dist/lib/bindings.js +936 -0
  35. package/dist/lib/bus-api.d.ts +18 -0
  36. package/dist/lib/bus-api.js +254 -0
  37. package/dist/lib/bus-follower.d.ts +464 -0
  38. package/dist/lib/bus-follower.js +1686 -0
  39. package/dist/lib/bus-leader.d.ts +292 -0
  40. package/dist/lib/bus-leader.js +2242 -0
  41. package/dist/lib/bus-transport.d.ts +64 -0
  42. package/dist/lib/bus-transport.js +140 -0
  43. package/dist/lib/bus.d.ts +518 -0
  44. package/dist/lib/bus.js +2055 -0
  45. package/dist/lib/channel-posts.d.ts +170 -0
  46. package/dist/lib/channel-posts.js +611 -0
  47. package/dist/lib/command-templates.d.ts +70 -0
  48. package/dist/lib/command-templates.js +744 -0
  49. package/dist/lib/commands.d.ts +541 -0
  50. package/dist/lib/commands.js +1155 -0
  51. package/dist/lib/config.d.ts +252 -0
  52. package/dist/lib/config.js +889 -0
  53. package/dist/lib/delivery.d.ts +163 -0
  54. package/dist/lib/delivery.js +542 -0
  55. package/dist/lib/extension.d.ts +7 -0
  56. package/dist/lib/extension.js +1604 -0
  57. package/dist/lib/generative-app-worker.mjs +104 -0
  58. package/dist/lib/generative-apps.d.ts +212 -0
  59. package/dist/lib/generative-apps.js +1006 -0
  60. package/dist/lib/inbound.d.ts +91 -0
  61. package/dist/lib/inbound.js +502 -0
  62. package/dist/lib/journal.d.ts +665 -0
  63. package/dist/lib/journal.js +3701 -0
  64. package/dist/lib/keyboard.d.ts +23 -0
  65. package/dist/lib/keyboard.js +37 -0
  66. package/dist/lib/lifecycle.d.ts +157 -0
  67. package/dist/lib/lifecycle.js +395 -0
  68. package/dist/lib/locks.d.ts +164 -0
  69. package/dist/lib/locks.js +1208 -0
  70. package/dist/lib/logging.d.ts +50 -0
  71. package/dist/lib/logging.js +274 -0
  72. package/dist/lib/media.d.ts +181 -0
  73. package/dist/lib/media.js +602 -0
  74. package/dist/lib/menu-model.d.ts +217 -0
  75. package/dist/lib/menu-model.js +663 -0
  76. package/dist/lib/menu-queue.d.ts +37 -0
  77. package/dist/lib/menu-queue.js +408 -0
  78. package/dist/lib/menu-settings.d.ts +115 -0
  79. package/dist/lib/menu-settings.js +595 -0
  80. package/dist/lib/menu-status.d.ts +32 -0
  81. package/dist/lib/menu-status.js +112 -0
  82. package/dist/lib/menu-thinking.d.ts +29 -0
  83. package/dist/lib/menu-thinking.js +82 -0
  84. package/dist/lib/menu.d.ts +171 -0
  85. package/dist/lib/menu.js +323 -0
  86. package/dist/lib/model.d.ts +127 -0
  87. package/dist/lib/model.js +409 -0
  88. package/dist/lib/outbound-attachments.d.ts +241 -0
  89. package/dist/lib/outbound-attachments.js +639 -0
  90. package/dist/lib/outbound-buttons.d.ts +69 -0
  91. package/dist/lib/outbound-buttons.js +248 -0
  92. package/dist/lib/outbound-markup.d.ts +42 -0
  93. package/dist/lib/outbound-markup.js +678 -0
  94. package/dist/lib/outbound-voice.d.ts +55 -0
  95. package/dist/lib/outbound-voice.js +152 -0
  96. package/dist/lib/outbound.d.ts +185 -0
  97. package/dist/lib/outbound.js +516 -0
  98. package/dist/lib/ownership.d.ts +77 -0
  99. package/dist/lib/ownership.js +174 -0
  100. package/dist/lib/paths.d.ts +40 -0
  101. package/dist/lib/paths.js +94 -0
  102. package/dist/lib/pi.d.ts +72 -0
  103. package/dist/lib/pi.js +121 -0
  104. package/dist/lib/polling.d.ts +351 -0
  105. package/dist/lib/polling.js +1196 -0
  106. package/dist/lib/preview.d.ts +192 -0
  107. package/dist/lib/preview.js +614 -0
  108. package/dist/lib/prompt-templates.d.ts +24 -0
  109. package/dist/lib/prompt-templates.js +118 -0
  110. package/dist/lib/prompts.d.ts +57 -0
  111. package/dist/lib/prompts.js +167 -0
  112. package/dist/lib/queue.d.ts +766 -0
  113. package/dist/lib/queue.js +1965 -0
  114. package/dist/lib/recovery.d.ts +86 -0
  115. package/dist/lib/recovery.js +285 -0
  116. package/dist/lib/rendering.d.ts +20 -0
  117. package/dist/lib/rendering.js +983 -0
  118. package/dist/lib/replies.d.ts +214 -0
  119. package/dist/lib/replies.js +737 -0
  120. package/dist/lib/routing.d.ts +207 -0
  121. package/dist/lib/routing.js +2282 -0
  122. package/dist/lib/runtime.d.ts +172 -0
  123. package/dist/lib/runtime.js +402 -0
  124. package/dist/lib/sections.d.ts +165 -0
  125. package/dist/lib/sections.js +327 -0
  126. package/dist/lib/setup.d.ts +82 -0
  127. package/dist/lib/setup.js +150 -0
  128. package/dist/lib/skills.d.ts +8 -0
  129. package/dist/lib/skills.js +12 -0
  130. package/dist/lib/status.d.ts +442 -0
  131. package/dist/lib/status.js +1008 -0
  132. package/dist/lib/sync.d.ts +179 -0
  133. package/dist/lib/sync.js +782 -0
  134. package/dist/lib/target.d.ts +20 -0
  135. package/dist/lib/target.js +27 -0
  136. package/dist/lib/telegram-api.d.ts +541 -0
  137. package/dist/lib/telegram-api.js +1159 -0
  138. package/dist/lib/text-groups.d.ts +89 -0
  139. package/dist/lib/text-groups.js +317 -0
  140. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  141. package/dist/lib/thread-cleanup-manager.js +560 -0
  142. package/dist/lib/thread-display.d.ts +46 -0
  143. package/dist/lib/thread-display.js +257 -0
  144. package/dist/lib/thread-naming.d.ts +46 -0
  145. package/dist/lib/thread-naming.js +78 -0
  146. package/dist/lib/thread-reconciler.d.ts +239 -0
  147. package/dist/lib/thread-reconciler.js +644 -0
  148. package/dist/lib/threads.d.ts +621 -0
  149. package/dist/lib/threads.js +3679 -0
  150. package/dist/lib/time-injection.d.ts +15 -0
  151. package/dist/lib/time-injection.js +56 -0
  152. package/dist/lib/turns.d.ts +109 -0
  153. package/dist/lib/turns.js +500 -0
  154. package/dist/lib/updates.d.ts +1290 -0
  155. package/dist/lib/updates.js +3634 -0
  156. package/dist/lib/voice.d.ts +117 -0
  157. package/dist/lib/voice.js +174 -0
  158. package/dist/lib/workspace-admission.d.ts +264 -0
  159. package/dist/lib/workspace-admission.js +1136 -0
  160. package/dist/lib/workspace-retirement.d.ts +219 -0
  161. package/dist/lib/workspace-retirement.js +587 -0
  162. package/dist/lib/workspace-slots.d.ts +30 -0
  163. package/dist/lib/workspace-slots.js +54 -0
  164. package/dist/package.json +126 -0
  165. package/dist/pi-telegram/index.js +1 -0
  166. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  167. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  168. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  169. package/dist/skills/generative-apps/SKILL.md +115 -0
  170. package/dist/skills/show-me/SKILL.md +166 -0
  171. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  172. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  173. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  174. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  175. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  176. package/docs/README.md +2 -0
  177. package/docs/architecture.md +1 -1
  178. package/docs/compact-matrix-literal.md +10 -3
  179. package/docs/generative-apps.md +15 -13
  180. package/docs/multi-instance-bus.md +6 -4
  181. package/docs/outbound.md +1 -1
  182. package/docs/public-api.md +3 -3
  183. package/lib/bindings.ts +89 -6
  184. package/lib/bus-follower.ts +7 -3
  185. package/lib/bus-leader.ts +44 -19
  186. package/lib/bus.ts +4 -1
  187. package/lib/commands.ts +2 -2
  188. package/lib/config.ts +14 -7
  189. package/lib/delivery.ts +38 -6
  190. package/lib/extension.ts +7 -0
  191. package/lib/generative-apps.ts +379 -13
  192. package/lib/menu-settings.ts +20 -6
  193. package/lib/outbound-markup.ts +16 -7
  194. package/lib/status.ts +15 -12
  195. package/lib/telegram-api.ts +12 -1
  196. package/lib/thread-display.ts +96 -18
  197. package/package.json +56 -13
  198. package/scripts/build-dist.mjs +44 -0
  199. package/scripts/measure-bus.mjs +8 -1
  200. package/scripts/measure-workspace.mjs +8 -1
  201. package/skills/generative-apps/SKILL.md +1 -1
  202. package/skills/show-me/SKILL.md +1 -1
  203. package/skills/show-me/references/telegram-surfaces.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
package/lib/bindings.ts CHANGED
@@ -10,6 +10,7 @@ import * as ChannelPosts from "./channel-posts.ts";
10
10
  import * as CommandTemplates from "./command-templates.ts";
11
11
  import * as Commands from "./commands.ts";
12
12
  import * as Config from "./config.ts";
13
+ import * as Delivery from "./delivery.ts";
13
14
  import * as Keyboard from "./keyboard.ts";
14
15
  import * as Lifecycle from "./lifecycle.ts";
15
16
  import * as Locks from "./locks.ts";
@@ -176,14 +177,47 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
176
177
  };
177
178
  }
178
179
 
180
+ export interface TelegramGenerativeAppLiveSurfaceBinding {
181
+ get: () =>
182
+ | GenerativeApps.GenerativeAppLiveSurfaceRuntime<GenerativeApps.TelegramBindLiveHandle>
183
+ | undefined;
184
+ set: (
185
+ runtime: GenerativeApps.GenerativeAppLiveSurfaceRuntime<GenerativeApps.TelegramBindLiveHandle>
186
+ | undefined,
187
+ ) => void;
188
+ shutdown: () => void;
189
+ }
190
+
191
+ export function createTelegramGenerativeAppLiveSurfaceBinding():
192
+ TelegramGenerativeAppLiveSurfaceBinding {
193
+ let current:
194
+ | GenerativeApps.GenerativeAppLiveSurfaceRuntime<GenerativeApps.TelegramBindLiveHandle>
195
+ | undefined;
196
+ return {
197
+ get() { return current; },
198
+ set(runtime) {
199
+ current?.shutdown();
200
+ current = runtime;
201
+ },
202
+ shutdown() {
203
+ current?.shutdown();
204
+ current = undefined;
205
+ },
206
+ };
207
+ }
208
+
179
209
  export function createTelegramGenerativeAppBoundButtonActionInvoker<
180
210
  TQuery extends {
181
- message?: { chat?: { id?: number }; message_id?: number };
211
+ message?: { chat?: { id?: number }; message_id?: number; message_thread_id?: number };
182
212
  },
183
213
  >(deps: {
184
214
  agentDir: string;
185
215
  assertExecutionCurrent: (query: TQuery) => void;
186
216
  getExecutionFence: (query: TQuery) => GenerativeApps.GenerativeAppExecutionFence | undefined;
217
+ getActiveProfileName?: () => string | undefined;
218
+ getLiveSurfaceRuntime?: () =>
219
+ | GenerativeApps.GenerativeAppLiveSurfaceRuntime<GenerativeApps.TelegramBindLiveHandle>
220
+ | undefined;
187
221
  planOutput: ReturnType<typeof OutboundHandlers.createTelegramOutboundReplyPlanner>;
188
222
  sendMarkdownReply: (
189
223
  chatId: number,
@@ -205,10 +239,29 @@ export function createTelegramGenerativeAppBoundButtonActionInvoker<
205
239
  ) => Promise<false | "new" | "edit"> {
206
240
  return async (action, query) => {
207
241
  let boundAction: GenerativeApps.GenerativeAppBoundAction | undefined;
242
+ let handedOff:
243
+ | GenerativeApps.GenerativeAppLiveSurface<GenerativeApps.TelegramBindLiveHandle>
244
+ | undefined;
208
245
  try {
209
246
  boundAction = GenerativeApps.parseGenerativeAppBoundAction(action.prompt);
210
247
  if (!boundAction) return false;
211
248
  deps.assertExecutionCurrent(query);
249
+ const chatId = query.message?.chat?.id;
250
+ const messageId = query.message?.message_id;
251
+ if (typeof chatId !== "number" || typeof messageId !== "number") {
252
+ throw new Error("Generative App callback target is unavailable.");
253
+ }
254
+ const liveSurfaces = deps.getLiveSurfaceRuntime?.();
255
+ handedOff = liveSurfaces?.take(GenerativeApps.getTelegramBindLiveSurfaceKey(
256
+ boundAction.app,
257
+ deps.getActiveProfileName?.() ?? "default",
258
+ {
259
+ chatId,
260
+ ...(query.message?.message_thread_id !== undefined
261
+ ? { threadId: query.message.message_thread_id }
262
+ : {}),
263
+ },
264
+ ));
212
265
  const result = await GenerativeApps.invokeGenerativeApp({
213
266
  agentDir: deps.agentDir,
214
267
  ...(deps.getExecutionFence(query)
@@ -227,11 +280,6 @@ export function createTelegramGenerativeAppBoundButtonActionInvoker<
227
280
  app: boundAction.app,
228
281
  });
229
282
  deps.assertExecutionCurrent(query);
230
- const chatId = query.message?.chat?.id;
231
- const messageId = query.message?.message_id;
232
- if (typeof chatId !== "number" || typeof messageId !== "number") {
233
- throw new Error("Generative App callback target is unavailable.");
234
- }
235
283
  const reply = deps.planOutput(result.output, {
236
284
  binding: {
237
285
  generation: result.generation,
@@ -239,6 +287,19 @@ export function createTelegramGenerativeAppBoundButtonActionInvoker<
239
287
  revision: result.revision,
240
288
  },
241
289
  });
290
+ if (handedOff && liveSurfaces) {
291
+ try {
292
+ await liveSurfaces.resume(handedOff, result);
293
+ deps.assertExecutionCurrent(query);
294
+ return "edit";
295
+ } catch (error) {
296
+ deps.recordRuntimeEvent("generative-app", error, {
297
+ phase: "bound-action-live-edit-fallback",
298
+ app: boundAction.app,
299
+ method: boundAction.method,
300
+ });
301
+ }
302
+ }
242
303
  if (result.viewMode === "edit" && deps.editInteractiveMessage) {
243
304
  let editFailed = false;
244
305
  try {
@@ -267,6 +328,7 @@ export function createTelegramGenerativeAppBoundButtonActionInvoker<
267
328
  deps.assertExecutionCurrent(query);
268
329
  return "new";
269
330
  } catch (error) {
331
+ if (handedOff) deps.getLiveSurfaceRuntime?.()?.open(handedOff);
270
332
  deps.recordRuntimeEvent("generative-app", error, {
271
333
  phase: "bound-action",
272
334
  ...(boundAction
@@ -534,6 +596,10 @@ interface TelegramCommandsAndToolsBindingDeps {
534
596
  resolveAgentTarget?: OutboundAttachments.TelegramOutboundMessageToolRegistrationDeps["resolveAgentTarget"];
535
597
  routeAgentMessage?: OutboundAttachments.TelegramOutboundMessageToolRegistrationDeps["routeAgentMessage"];
536
598
  canSendDirect: () => boolean;
599
+ setGenerativeAppLiveSurfaceRuntime?: (
600
+ runtime: GenerativeApps.GenerativeAppLiveSurfaceRuntime<GenerativeApps.TelegramBindLiveHandle>
601
+ | undefined,
602
+ ) => void;
537
603
  updateStatus: TelegramBridgeStatusUpdater;
538
604
  recordRuntimeEvent: TelegramRuntimeEventRecorder;
539
605
  }
@@ -565,17 +631,31 @@ export function registerTelegramCommandsAndTools({
565
631
  resolveAgentTarget,
566
632
  routeAgentMessage,
567
633
  canSendDirect,
634
+ setGenerativeAppLiveSurfaceRuntime,
568
635
  recordRuntimeEvent,
569
636
  updateStatus,
570
637
  }: TelegramCommandsAndToolsBindingDeps): void {
571
638
  GenerativeApps.registerTelegramBindTool(pi, {
572
639
  agentDir,
640
+ getActiveProfileName: configStore.getActiveProfileName,
573
641
  getActiveTurn: activeTurnRuntime.get,
642
+ ...(setGenerativeAppLiveSurfaceRuntime
643
+ ? { setLiveSurfaceRuntime: setGenerativeAppLiveSurfaceRuntime }
644
+ : {}),
645
+ isDeliveryHandleCurrent: Delivery.isTelegramDeliveryHandleCurrent,
646
+ editView: (handle, view) => Delivery.editTelegramView(
647
+ handle as Delivery.TelegramDeliveryHandle,
648
+ view as Delivery.TelegramDeliveryView,
649
+ ),
574
650
  planOutput: OutboundHandlers.createTelegramOutboundReplyPlanner(
575
651
  buttonActionStore,
576
652
  Config.createTelegramConfigControls(configStore).getAssistantRenderingMode,
577
653
  ),
578
654
  sendMarkdownReply,
655
+ sendView: (view, options) => Delivery.sendTelegramView(
656
+ view as Delivery.TelegramDeliveryView,
657
+ options,
658
+ ),
579
659
  recordRuntimeEvent,
580
660
  });
581
661
  ChannelPosts.registerTelegramChannelPostMutationTool(pi, { mutate: mutateChannelPost });
@@ -782,6 +862,7 @@ interface TelegramLifecycleBindingDeps {
782
862
  deferredQueueDispatchRuntime: Queue.TelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>;
783
863
  modelContextAvailabilityRuntime: Prompts.TelegramModelContextAvailabilityRuntime;
784
864
  disconnectOnQuit?: () => Promise<unknown>;
865
+ shutdownGenerativeAppLiveSurfaces?: () => void;
785
866
  resolveAutomaticThreadCleanupEnabled?: () => boolean | Promise<boolean>;
786
867
  buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
787
868
  callMultipart: OutboundHandlers.TelegramVoiceReplySenderDeps["sendMultipart"];
@@ -871,6 +952,7 @@ export function registerTelegramLifecycleRuntimeHooks({
871
952
  deferredQueueDispatchRuntime,
872
953
  modelContextAvailabilityRuntime,
873
954
  disconnectOnQuit,
955
+ shutdownGenerativeAppLiveSurfaces,
874
956
  resolveAutomaticThreadCleanupEnabled,
875
957
  buttonActionStore,
876
958
  callMultipart,
@@ -1194,6 +1276,7 @@ export function registerTelegramLifecycleRuntimeHooks({
1194
1276
  },
1195
1277
  async onSessionShutdown(event, ctx) {
1196
1278
  if (!isSessionContextActive(ctx)) return;
1279
+ shutdownGenerativeAppLiveSurfaces?.();
1197
1280
  agentLifecycleHooks.clearRetainedAgentEnd();
1198
1281
  activityRuntime.onSessionShutdown();
1199
1282
  activityVerbosityRuntime?.reset();
@@ -43,6 +43,7 @@ import {
43
43
  type TelegramBusSocketPathSource,
44
44
  TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME,
45
45
  TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE,
46
+ TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT,
46
47
  } from "./bus.ts";
47
48
  import type { TelegramConfigStore, TelegramThreadDisplayMode } from "./config.ts";
48
49
  import {
@@ -1914,9 +1915,12 @@ export function createTelegramBusFollowerRegistrationRuntime<
1914
1915
  if (!socketPath || !generation || !deps.registrationState?.isRegistered()) {
1915
1916
  throw new Error("Telegram follower is not registered with the leader.");
1916
1917
  }
1917
- if (!hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE) ||
1918
- !hasTelegramBusCapability(deps.registrationState.getLeaderProtocol(), TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE)) {
1919
- throw new Error("The Telegram peers do not support Thread display settings. Update or restart both instances.");
1918
+ const requiredCapability = mode === "directory-snake" || mode === "directory-title"
1919
+ ? TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT
1920
+ : TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE;
1921
+ if (!hasTelegramBusCapability(deps.protocolIdentity, requiredCapability) ||
1922
+ !hasTelegramBusCapability(deps.registrationState.getLeaderProtocol(), requiredCapability)) {
1923
+ throw new Error("The Telegram peers do not support this Thread display setting. Update or restart both instances.");
1920
1924
  }
1921
1925
  const requestId = deps.createRequestId();
1922
1926
  const response = await sendTelegramBusLocalEnvelope({
package/lib/bus-leader.ts CHANGED
@@ -9,6 +9,7 @@ import * as Sync from "./sync.ts";
9
9
  import {
10
10
  createTelegramThreadDisplayReconciler,
11
11
  resolveTelegramInitialWorkspaceDisplayName,
12
+ resolveTelegramLiveWorkspaceBindingKeys,
12
13
  } from "./thread-display.ts";
13
14
  import type { TelegramThreadDisplayMode } from "./config.ts";
14
15
  import * as ThreadReconciler from "./thread-reconciler.ts";
@@ -40,6 +41,7 @@ import {
40
41
  TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT,
41
42
  TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME,
42
43
  TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE,
44
+ TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT,
43
45
  } from "./bus.ts";
44
46
  import { getTelegramBusTransportRetryPolicy } from "./bus-transport.ts";
45
47
  import type { TelegramQueueHandoffPayload } from "./queue.ts";
@@ -309,6 +311,13 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
309
311
  });
310
312
  },
311
313
  }).run;
314
+ const captureLiveBindingKeys = (
315
+ bindings: readonly Threads.TelegramWorkspaceThreadBinding[],
316
+ ): ReadonlySet<string> => resolveTelegramLiveWorkspaceBindingKeys(
317
+ bindings,
318
+ deps.topicTargetStore.getActiveByInstanceId(deps.instanceId)?.target,
319
+ deps.runtime.followerRegistry.list(),
320
+ );
312
321
  const provisionerPorts = {
313
322
  getAllowedUserId: deps.getAllowedUserId,
314
323
  topicTargetStore: deps.topicTargetStore,
@@ -327,6 +336,9 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
327
336
  bindings: deps.topicTargetStore.listWorkspaceBindings(),
328
337
  binding,
329
338
  mode: deps.getThreadDisplayMode!(),
339
+ liveBindingKeys: captureLiveBindingKeys(
340
+ deps.topicTargetStore.listWorkspaceBindings(),
341
+ ),
330
342
  });
331
343
  },
332
344
  }
@@ -339,6 +351,7 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
339
351
  getMode: deps.getThreadDisplayMode,
340
352
  getProfileKey: () => deps.getTelegramProfile?.() ?? "default",
341
353
  getLeaderEpoch: () => deps.getCurrentLeaderEpoch?.(),
354
+ captureLiveBindingKeys,
342
355
  captureBindingAuthority(binding) {
343
356
  const matches = (target: TelegramTarget | undefined) =>
344
357
  target?.chatId === binding.target.chatId && target?.threadId === binding.target.threadId;
@@ -392,8 +405,11 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
392
405
  throw new Error("Telegram Thread display setting requires current leader authority.");
393
406
  }
394
407
  const assertDisplayPeers = () => {
408
+ const requiredCapability = mode === "directory-snake" || mode === "directory-title"
409
+ ? TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT
410
+ : TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE;
395
411
  if (mode !== "names" && deps.runtime.followerRegistry.list().some((follower) =>
396
- !hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE),
412
+ !hasTelegramBusCapability(follower.protocol, requiredCapability),
397
413
  )) throw new Error("Update or restart all connected followers before changing Thread display mode.");
398
414
  };
399
415
  assertDisplayPeers();
@@ -597,14 +613,17 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
597
613
  );
598
614
  return binding?.displayTitle ?? binding?.threadName;
599
615
  },
600
- onFollowerDisconnected: (follower) => runWorkspaceOperation(
601
- {
602
- operationId: createTelegramWorkspaceAdmissionOperationId(),
603
- operationKind: "workspace.disconnect-follower",
604
- scopes: [{ kind: "profile" }],
605
- },
606
- () => disconnectFollower(follower),
607
- ),
616
+ onFollowerDisconnected: async (follower) => {
617
+ await runWorkspaceOperation(
618
+ {
619
+ operationId: createTelegramWorkspaceAdmissionOperationId(),
620
+ operationKind: "workspace.disconnect-follower",
621
+ scopes: [{ kind: "profile" }],
622
+ },
623
+ () => disconnectFollower(follower),
624
+ );
625
+ scheduleDisplay();
626
+ },
608
627
  async renameFollowerThread(follower, threadName) {
609
628
  return runWorkspaceOperation({
610
629
  operationId: createTelegramWorkspaceAdmissionOperationId(),
@@ -692,14 +711,17 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
692
711
  });
693
712
  },
694
713
  ),
695
- onFollowerConfirmedDead: (follower) => runWorkspaceOperation(
696
- {
697
- operationId: createTelegramWorkspaceAdmissionOperationId(),
698
- operationKind: "workspace.cleanup-dead-follower",
699
- scopes: [{ kind: "profile" }],
700
- },
701
- () => cleanupConfirmedDeadFollower(follower),
702
- ),
714
+ onFollowerConfirmedDead: async (follower) => {
715
+ await runWorkspaceOperation(
716
+ {
717
+ operationId: createTelegramWorkspaceAdmissionOperationId(),
718
+ operationKind: "workspace.cleanup-dead-follower",
719
+ scopes: [{ kind: "profile" }],
720
+ },
721
+ () => cleanupConfirmedDeadFollower(follower),
722
+ );
723
+ scheduleDisplay();
724
+ },
703
725
  provisionFollowerTarget: (registration, options) => runWorkspaceOperation(
704
726
  {
705
727
  operationId: `follower-provision:${registration.instanceId}:${registration.registrationGeneration}`,
@@ -2243,9 +2265,12 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
2243
2265
  const current = () => epoch !== undefined && deps.getCurrentLeaderEpoch?.() === epoch &&
2244
2266
  deps.followerRegistry.get(envelope.instanceId)?.registrationGeneration === envelope.registrationGeneration;
2245
2267
  const follower = deps.followerRegistry.get(envelope.instanceId);
2268
+ const requiredCapability = envelope.mode === "directory-snake" || envelope.mode === "directory-title"
2269
+ ? TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT
2270
+ : TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE;
2246
2271
  if (!current() || !follower?.registrationGeneration || !deps.applyThreadDisplayMode ||
2247
- !hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE) ||
2248
- !hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE)) {
2272
+ !hasTelegramBusCapability(deps.protocolIdentity, requiredCapability) ||
2273
+ !hasTelegramBusCapability(follower.protocol, requiredCapability)) {
2249
2274
  return { kind: "bus.ack", requestId: envelope.requestId, ok: false,
2250
2275
  message: "Thread display settings require current registration and compatible leader authority." };
2251
2276
  }
package/lib/bus.ts CHANGED
@@ -230,6 +230,8 @@ export const TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE =
230
230
  export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME =
231
231
  "workspace-thread-rename-v1" as const;
232
232
  export const TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE = "thread-display-mode-v1" as const;
233
+ export const TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT =
234
+ "directory-display-format-v1" as const;
233
235
  export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT =
234
236
  "workspace-follower-auto-connect-v1" as const;
235
237
 
@@ -1027,7 +1029,8 @@ export function parseTelegramBusEnvelope(
1027
1029
  case "follower.setThreadDisplayMode":
1028
1030
  if (typeof value.instanceId === "string" &&
1029
1031
  typeof value.registrationGeneration === "string" &&
1030
- (value.mode === "letters" || value.mode === "names" || value.mode === "directories")) {
1032
+ (value.mode === "letters" || value.mode === "names" ||
1033
+ value.mode === "directory-snake" || value.mode === "directory-title")) {
1031
1034
  envelope = { kind, requestId, instanceId: value.instanceId,
1032
1035
  registrationGeneration: value.registrationGeneration, mode: value.mode };
1033
1036
  }
package/lib/commands.ts CHANGED
@@ -1205,7 +1205,7 @@ export const TELEGRAM_APP_MENU_INTRO_HTML = [
1205
1205
  "<b>Pi Telegram</b>",
1206
1206
  "",
1207
1207
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
1208
- `${formatTelegramCommandEmojiPrefix("name")}/name Name — Rename this thread`,
1208
+ `${formatTelegramCommandEmojiPrefix("name")}/name — Rename this thread`,
1209
1209
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
1210
1210
  `${formatTelegramCommandEmojiPrefix("next")}/next — Force next turn`,
1211
1211
  `${formatTelegramCommandEmojiPrefix("continue")}/continue — Queue continue prompt`,
@@ -1246,7 +1246,7 @@ function buildTelegramAppMenuIntroHtml(): string {
1246
1246
  "<b>Pi Telegram</b>",
1247
1247
  "",
1248
1248
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
1249
- `${formatTelegramCommandEmojiPrefix("name")}/name Name — Rename this thread`,
1249
+ `${formatTelegramCommandEmojiPrefix("name")}/name — Rename this thread`,
1250
1250
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
1251
1251
  ...extensionLines,
1252
1252
  `${formatTelegramCommandEmojiPrefix("next")}/next — Force next turn`,
package/lib/config.ts CHANGED
@@ -136,16 +136,23 @@ export interface ResolvedTelegramTimeConfig {
136
136
  timezone: string;
137
137
  }
138
138
 
139
- export type TelegramThreadDisplayMode = "letters" | "names" | "directories";
139
+ export type TelegramThreadDisplayMode =
140
+ | "letters"
141
+ | "names"
142
+ | "directories"
143
+ | "directory-snake"
144
+ | "directory-title";
145
+
146
+ const TELEGRAM_THREAD_DISPLAY_MODES: readonly TelegramThreadDisplayMode[] = [
147
+ "letters", "names", "directory-snake", "directory-title",
148
+ ];
140
149
 
141
150
  export function resolveTelegramThreadDisplayMode(
142
151
  config: Pick<TelegramConfig, "threadDisplayMode">,
143
152
  ): TelegramThreadDisplayMode {
144
- return config.threadDisplayMode === "names"
145
- ? "names"
146
- : config.threadDisplayMode === "directories"
147
- ? "directories"
148
- : "letters";
153
+ return TELEGRAM_THREAD_DISPLAY_MODES.includes(config.threadDisplayMode as TelegramThreadDisplayMode)
154
+ ? config.threadDisplayMode as TelegramThreadDisplayMode
155
+ : "letters";
149
156
  }
150
157
 
151
158
  export async function setTelegramThreadDisplayMode(
@@ -153,7 +160,7 @@ export async function setTelegramThreadDisplayMode(
153
160
  mode: TelegramThreadDisplayMode,
154
161
  isCurrent: () => boolean,
155
162
  ): Promise<void> {
156
- if (!["letters", "names", "directories"].includes(mode)) {
163
+ if (!TELEGRAM_THREAD_DISPLAY_MODES.includes(mode)) {
157
164
  throw new Error("Invalid Telegram Thread display mode.");
158
165
  }
159
166
  const profile = store.getActiveProfileName();
package/lib/delivery.ts CHANGED
@@ -18,7 +18,10 @@ import {
18
18
  type TelegramTarget,
19
19
  } from "./target.ts";
20
20
  import {
21
+ getTelegramApiRetryAfterMs,
22
+ isRetryableTelegramApiError,
21
23
  isTelegramApiCommitUnknownError,
24
+ isTelegramMessageUnavailableError,
22
25
  type TelegramBridgeApiRuntime,
23
26
  } from "./telegram-api.ts";
24
27
 
@@ -60,6 +63,9 @@ export type TelegramDeliveryFailureReason =
60
63
  | "stale-handle"
61
64
  | "invalid-view"
62
65
  | "commit-unknown"
66
+ | "message-unavailable"
67
+ | "rate-limited"
68
+ | "transport-retryable"
63
69
  | "transport-failed";
64
70
 
65
71
  export type TelegramDeliveryResult<T> =
@@ -68,6 +74,8 @@ export type TelegramDeliveryResult<T> =
68
74
  ok: false;
69
75
  reason: TelegramDeliveryFailureReason;
70
76
  message: string;
77
+ /** Exact Telegram flood-control delay when supplied by the API. */
78
+ retryAfterMs?: number;
71
79
  /** Successfully materialized state that callers may edit or delete to recover. */
72
80
  partial?: T;
73
81
  };
@@ -246,10 +254,26 @@ function failure<T>(
246
254
  reason: TelegramDeliveryFailureReason,
247
255
  message: string,
248
256
  partial?: T,
257
+ retryAfterMs?: number,
249
258
  ): TelegramDeliveryResult<T> {
259
+ const retry = retryAfterMs === undefined ? {} : { retryAfterMs };
250
260
  return partial === undefined
251
- ? { ok: false, reason, message }
252
- : { ok: false, reason, message, partial };
261
+ ? { ok: false, reason, message, ...retry }
262
+ : { ok: false, reason, message, partial, ...retry };
263
+ }
264
+
265
+ export function classifyTelegramDeliveryTransportError(error: unknown): {
266
+ reason: Extract<TelegramDeliveryFailureReason,
267
+ "commit-unknown" | "message-unavailable" | "rate-limited" |
268
+ "transport-retryable" | "transport-failed">;
269
+ retryAfterMs?: number;
270
+ } {
271
+ if (isTelegramApiCommitUnknownError(error)) return { reason: "commit-unknown" };
272
+ if (isTelegramMessageUnavailableError(error)) return { reason: "message-unavailable" };
273
+ const retryAfterMs = getTelegramApiRetryAfterMs(error);
274
+ if (retryAfterMs !== undefined) return { reason: "rate-limited", retryAfterMs };
275
+ if (isRetryableTelegramApiError(error)) return { reason: "transport-retryable" };
276
+ return { reason: "transport-failed" };
253
277
  }
254
278
 
255
279
  /** @internal */
@@ -479,14 +503,14 @@ export function createTelegramDeliveryRuntime(
479
503
  if (error instanceof TelegramDeliveryTransportGenerationError) {
480
504
  return inactive();
481
505
  }
506
+ const classified = classifyTelegramDeliveryTransportError(error);
482
507
  return failure(
483
- isTelegramApiCommitUnknownError(error)
484
- ? "commit-unknown"
485
- : "transport-failed",
486
- isTelegramApiCommitUnknownError(error)
508
+ classified.reason,
509
+ classified.reason === "commit-unknown"
487
510
  ? `Telegram delivery ${operation} may have committed before transport failed.`
488
511
  : `Telegram delivery ${operation} failed.`,
489
512
  partial,
513
+ classified.retryAfterMs,
490
514
  );
491
515
  };
492
516
  const createHandle = (
@@ -837,6 +861,14 @@ export function clearTelegramDeliveryRuntime(): void {
837
861
  registry.runtime = undefined;
838
862
  }
839
863
 
864
+ /** @internal */
865
+ export function isTelegramDeliveryHandleCurrent(
866
+ handle: TelegramDeliveryHandle,
867
+ ): boolean {
868
+ const runtime = getTelegramDeliveryRuntimeRegistry().runtime;
869
+ return runtime !== undefined && runtime.generation === handle.generation;
870
+ }
871
+
840
872
  export async function sendTelegramView(
841
873
  view: TelegramDeliveryView,
842
874
  options: SendTelegramViewOptions,
package/lib/extension.ts CHANGED
@@ -64,6 +64,7 @@ const telegramBusProtocolIdentity =
64
64
  Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT,
65
65
  Bus.TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME,
66
66
  Bus.TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE,
67
+ Bus.TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT,
67
68
  ]);
68
69
 
69
70
  // --- Extension Runtime ---
@@ -672,11 +673,15 @@ export default function (pi: Pi.ExtensionAPI) {
672
673
  getHandlers: configStore.getOutboundHandlers,
673
674
  recordRuntimeEvent,
674
675
  });
676
+ const generativeAppLiveSurfaceBinding =
677
+ Bindings.createTelegramGenerativeAppLiveSurfaceBinding();
675
678
  const invokeGenerativeAppBoundButtonAction =
676
679
  Bindings.createTelegramGenerativeAppBoundButtonActionInvoker({
677
680
  agentDir: Paths.resolveAgentDir(),
678
681
  assertExecutionCurrent: Updates.assertTelegramUpdateExecutionCurrent,
679
682
  getExecutionFence: Updates.getTelegramUpdateExecutionFence,
683
+ getActiveProfileName: configStore.getActiveProfileName,
684
+ getLiveSurfaceRuntime: generativeAppLiveSurfaceBinding.get,
680
685
  planOutput: planGenerativeAppOutput,
681
686
  sendMarkdownReply,
682
687
  editInteractiveMessage,
@@ -1803,6 +1808,7 @@ export default function (pi: Pi.ExtensionAPI) {
1803
1808
  getDefaultChatId: proactivePushChatIdGetter,
1804
1809
  getDefaultTarget: proactivePushTargetGetter,
1805
1810
  ...agentMessageToolRoutingRuntime,
1811
+ setGenerativeAppLiveSurfaceRuntime: generativeAppLiveSurfaceBinding.set,
1806
1812
  updateStatus,
1807
1813
  recordRuntimeEvent,
1808
1814
  });
@@ -1831,6 +1837,7 @@ export default function (pi: Pi.ExtensionAPI) {
1831
1837
  deferredQueueDispatchRuntime,
1832
1838
  modelContextAvailabilityRuntime,
1833
1839
  disconnectOnQuit: cleanupTelegramThreadForSessionRestart,
1840
+ shutdownGenerativeAppLiveSurfaces: generativeAppLiveSurfaceBinding.shutdown,
1834
1841
  resolveAutomaticThreadCleanupEnabled:
1835
1842
  configControls.resolveAutomaticThreadCleanupEnabled,
1836
1843
  buttonActionStore,