@llblab/pi-telegram 0.45.11 → 0.47.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/BACKLOG.md +5 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +6 -6
  4. package/dist/api/activity.d.ts +6 -0
  5. package/dist/api/activity.js +6 -0
  6. package/dist/api/commands.d.ts +6 -0
  7. package/dist/api/commands.js +6 -0
  8. package/dist/api/delivery.d.ts +6 -0
  9. package/dist/api/delivery.js +6 -0
  10. package/dist/api/inbound.d.ts +6 -0
  11. package/dist/api/inbound.js +6 -0
  12. package/dist/api/keyboard.d.ts +6 -0
  13. package/dist/api/keyboard.js +6 -0
  14. package/dist/api/outbound.d.ts +6 -0
  15. package/dist/api/outbound.js +6 -0
  16. package/dist/api/sections.d.ts +7 -0
  17. package/dist/api/sections.js +6 -0
  18. package/dist/api/status.d.ts +6 -0
  19. package/dist/api/status.js +6 -0
  20. package/dist/api/updates.d.ts +6 -0
  21. package/dist/api/updates.js +6 -0
  22. package/dist/api/voice.d.ts +6 -0
  23. package/dist/api/voice.js +6 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +6 -0
  26. package/dist/lib/activity-verbosity.d.ts +52 -0
  27. package/dist/lib/activity-verbosity.js +596 -0
  28. package/dist/lib/activity.d.ts +220 -0
  29. package/dist/lib/activity.js +574 -0
  30. package/dist/lib/agent-messages.d.ts +28 -0
  31. package/dist/lib/agent-messages.js +86 -0
  32. package/dist/lib/bindings.d.ts +250 -0
  33. package/dist/lib/bindings.js +936 -0
  34. package/dist/lib/bus-api.d.ts +18 -0
  35. package/dist/lib/bus-api.js +254 -0
  36. package/dist/lib/bus-follower.d.ts +464 -0
  37. package/dist/lib/bus-follower.js +1686 -0
  38. package/dist/lib/bus-leader.d.ts +292 -0
  39. package/dist/lib/bus-leader.js +2242 -0
  40. package/dist/lib/bus-transport.d.ts +64 -0
  41. package/dist/lib/bus-transport.js +140 -0
  42. package/dist/lib/bus.d.ts +518 -0
  43. package/dist/lib/bus.js +2055 -0
  44. package/dist/lib/channel-posts.d.ts +170 -0
  45. package/dist/lib/channel-posts.js +611 -0
  46. package/dist/lib/command-templates.d.ts +70 -0
  47. package/dist/lib/command-templates.js +744 -0
  48. package/dist/lib/commands.d.ts +541 -0
  49. package/dist/lib/commands.js +1155 -0
  50. package/dist/lib/config.d.ts +252 -0
  51. package/dist/lib/config.js +889 -0
  52. package/dist/lib/delivery.d.ts +163 -0
  53. package/dist/lib/delivery.js +542 -0
  54. package/dist/lib/extension.d.ts +7 -0
  55. package/dist/lib/extension.js +1608 -0
  56. package/dist/lib/generative-app-worker.mjs +104 -0
  57. package/dist/lib/generative-apps.d.ts +212 -0
  58. package/dist/lib/generative-apps.js +1006 -0
  59. package/dist/lib/inbound.d.ts +91 -0
  60. package/dist/lib/inbound.js +502 -0
  61. package/dist/lib/journal.d.ts +665 -0
  62. package/dist/lib/journal.js +3701 -0
  63. package/dist/lib/keyboard.d.ts +23 -0
  64. package/dist/lib/keyboard.js +37 -0
  65. package/dist/lib/lifecycle.d.ts +157 -0
  66. package/dist/lib/lifecycle.js +395 -0
  67. package/dist/lib/locks.d.ts +164 -0
  68. package/dist/lib/locks.js +1208 -0
  69. package/dist/lib/logging.d.ts +50 -0
  70. package/dist/lib/logging.js +274 -0
  71. package/dist/lib/media.d.ts +181 -0
  72. package/dist/lib/media.js +602 -0
  73. package/dist/lib/menu-model.d.ts +217 -0
  74. package/dist/lib/menu-model.js +663 -0
  75. package/dist/lib/menu-queue.d.ts +37 -0
  76. package/dist/lib/menu-queue.js +408 -0
  77. package/dist/lib/menu-settings.d.ts +116 -0
  78. package/dist/lib/menu-settings.js +595 -0
  79. package/dist/lib/menu-status.d.ts +32 -0
  80. package/dist/lib/menu-status.js +112 -0
  81. package/dist/lib/menu-thinking.d.ts +29 -0
  82. package/dist/lib/menu-thinking.js +82 -0
  83. package/dist/lib/menu.d.ts +171 -0
  84. package/dist/lib/menu.js +323 -0
  85. package/dist/lib/model.d.ts +127 -0
  86. package/dist/lib/model.js +409 -0
  87. package/dist/lib/outbound-attachments.d.ts +241 -0
  88. package/dist/lib/outbound-attachments.js +639 -0
  89. package/dist/lib/outbound-buttons.d.ts +69 -0
  90. package/dist/lib/outbound-buttons.js +248 -0
  91. package/dist/lib/outbound-markup.d.ts +42 -0
  92. package/dist/lib/outbound-markup.js +678 -0
  93. package/dist/lib/outbound-voice.d.ts +55 -0
  94. package/dist/lib/outbound-voice.js +152 -0
  95. package/dist/lib/outbound.d.ts +185 -0
  96. package/dist/lib/outbound.js +516 -0
  97. package/dist/lib/ownership.d.ts +77 -0
  98. package/dist/lib/ownership.js +174 -0
  99. package/dist/lib/paths.d.ts +40 -0
  100. package/dist/lib/paths.js +94 -0
  101. package/dist/lib/pi.d.ts +72 -0
  102. package/dist/lib/pi.js +121 -0
  103. package/dist/lib/polling.d.ts +351 -0
  104. package/dist/lib/polling.js +1196 -0
  105. package/dist/lib/preview.d.ts +192 -0
  106. package/dist/lib/preview.js +614 -0
  107. package/dist/lib/prompt-templates.d.ts +24 -0
  108. package/dist/lib/prompt-templates.js +118 -0
  109. package/dist/lib/prompts.d.ts +57 -0
  110. package/dist/lib/prompts.js +167 -0
  111. package/dist/lib/queue.d.ts +766 -0
  112. package/dist/lib/queue.js +1965 -0
  113. package/dist/lib/recovery.d.ts +86 -0
  114. package/dist/lib/recovery.js +285 -0
  115. package/dist/lib/rendering.d.ts +20 -0
  116. package/dist/lib/rendering.js +983 -0
  117. package/dist/lib/replies.d.ts +214 -0
  118. package/dist/lib/replies.js +737 -0
  119. package/dist/lib/routing.d.ts +207 -0
  120. package/dist/lib/routing.js +2282 -0
  121. package/dist/lib/runtime.d.ts +172 -0
  122. package/dist/lib/runtime.js +402 -0
  123. package/dist/lib/sections.d.ts +165 -0
  124. package/dist/lib/sections.js +327 -0
  125. package/dist/lib/setup.d.ts +82 -0
  126. package/dist/lib/setup.js +150 -0
  127. package/dist/lib/skills.d.ts +8 -0
  128. package/dist/lib/skills.js +12 -0
  129. package/dist/lib/status.d.ts +442 -0
  130. package/dist/lib/status.js +1006 -0
  131. package/dist/lib/sync.d.ts +179 -0
  132. package/dist/lib/sync.js +782 -0
  133. package/dist/lib/target.d.ts +20 -0
  134. package/dist/lib/target.js +27 -0
  135. package/dist/lib/telegram-api.d.ts +541 -0
  136. package/dist/lib/telegram-api.js +1159 -0
  137. package/dist/lib/text-groups.d.ts +89 -0
  138. package/dist/lib/text-groups.js +317 -0
  139. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  140. package/dist/lib/thread-cleanup-manager.js +560 -0
  141. package/dist/lib/thread-display.d.ts +46 -0
  142. package/dist/lib/thread-display.js +257 -0
  143. package/dist/lib/thread-naming.d.ts +46 -0
  144. package/dist/lib/thread-naming.js +78 -0
  145. package/dist/lib/thread-reconciler.d.ts +239 -0
  146. package/dist/lib/thread-reconciler.js +644 -0
  147. package/dist/lib/threads.d.ts +621 -0
  148. package/dist/lib/threads.js +3679 -0
  149. package/dist/lib/time-injection.d.ts +15 -0
  150. package/dist/lib/time-injection.js +56 -0
  151. package/dist/lib/turns.d.ts +109 -0
  152. package/dist/lib/turns.js +500 -0
  153. package/dist/lib/updates.d.ts +1290 -0
  154. package/dist/lib/updates.js +3634 -0
  155. package/dist/lib/voice.d.ts +117 -0
  156. package/dist/lib/voice.js +174 -0
  157. package/dist/lib/workspace-admission.d.ts +264 -0
  158. package/dist/lib/workspace-admission.js +1136 -0
  159. package/dist/lib/workspace-retirement.d.ts +219 -0
  160. package/dist/lib/workspace-retirement.js +587 -0
  161. package/dist/lib/workspace-slots.d.ts +30 -0
  162. package/dist/lib/workspace-slots.js +54 -0
  163. package/dist/package.json +126 -0
  164. package/dist/pi-telegram/index.js +1 -0
  165. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  166. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  167. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  168. package/dist/skills/generative-apps/SKILL.md +115 -0
  169. package/dist/skills/show-me/SKILL.md +166 -0
  170. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  171. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  172. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  173. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  174. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  175. package/docs/architecture.md +1 -1
  176. package/docs/compact-matrix-literal.md +10 -3
  177. package/docs/generative-apps.md +15 -13
  178. package/docs/multi-instance-bus.md +17 -3
  179. package/docs/outbound.md +1 -1
  180. package/docs/public-api.md +2 -2
  181. package/lib/bindings.ts +89 -6
  182. package/lib/bus-follower.ts +11 -3
  183. package/lib/bus-leader.ts +57 -20
  184. package/lib/bus.ts +12 -2
  185. package/lib/commands.ts +2 -2
  186. package/lib/config.ts +14 -7
  187. package/lib/delivery.ts +38 -6
  188. package/lib/extension.ts +26 -1
  189. package/lib/generative-apps.ts +379 -13
  190. package/lib/menu-settings.ts +21 -5
  191. package/lib/outbound-markup.ts +16 -7
  192. package/lib/pi.ts +4 -0
  193. package/lib/sync.ts +3 -0
  194. package/lib/telegram-api.ts +12 -1
  195. package/lib/thread-cleanup-manager.ts +36 -6
  196. package/lib/thread-display.ts +96 -18
  197. package/lib/threads.ts +150 -30
  198. package/package.json +56 -13
  199. package/scripts/build-dist.mjs +44 -0
  200. package/scripts/measure-bus.mjs +8 -1
  201. package/scripts/measure-workspace.mjs +8 -1
  202. package/skills/generative-apps/SKILL.md +1 -1
  203. package/skills/show-me/SKILL.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,936 @@
1
+ /**
2
+ * Telegram bridge binding composition
3
+ * Zones: telegram, pi agent, orchestration
4
+ * Owns pi-facing tool, command, and lifecycle hook registration for the entrypoint
5
+ */
6
+ import * as Activity from "./activity.js";
7
+ import * as ActivityVerbosity from "./activity-verbosity.js";
8
+ import * as ChannelPosts from "./channel-posts.js";
9
+ import * as CommandTemplates from "./command-templates.js";
10
+ import * as Commands from "./commands.js";
11
+ import * as Config from "./config.js";
12
+ import * as Delivery from "./delivery.js";
13
+ import * as Lifecycle from "./lifecycle.js";
14
+ import * as OutboundAttachments from "./outbound-attachments.js";
15
+ import * as OutboundHandlers from "./outbound.js";
16
+ import * as Pi from "./pi.js";
17
+ import * as Prompts from "./prompts.js";
18
+ import * as Queue from "./queue.js";
19
+ import * as Replies from "./replies.js";
20
+ import * as Routing from "./routing.js";
21
+ import * as Runtime from "./runtime.js";
22
+ import * as Setup from "./setup.js";
23
+ import * as TelegramApi from "./telegram-api.js";
24
+ import * as GenerativeApps from "./generative-apps.js";
25
+ export function createTelegramQueueBindingRuntime(deps) {
26
+ const settleDiscardedItems = (items, ctx) => {
27
+ const durableItems = items.filter((item) => (item.admissionReceipts?.length ?? 0) > 0);
28
+ if (durableItems.length === 0)
29
+ return true;
30
+ const settlement = deps.admission.getSettlement();
31
+ if (!settlement)
32
+ return false;
33
+ settlement.onItemsDiscarded(durableItems, ctx);
34
+ return durableItems.every((item) => !settlement.isItemReady(item));
35
+ };
36
+ const mutation = Queue.createTelegramQueueMutationController({
37
+ ...deps.store,
38
+ allocateLaneOrder: deps.queue.allocateItemOrder,
39
+ onItemsDiscarded(items, ctx) {
40
+ if (!settleDiscardedItems(items, ctx)) {
41
+ throw new Error("Telegram queue items could not be discarded durably.");
42
+ }
43
+ },
44
+ updateStatus: deps.updateStatus,
45
+ recordRuntimeEvent: deps.recordRuntimeEvent,
46
+ });
47
+ const dispatch = Queue.createTelegramQueueDispatchRuntime({
48
+ ...deps.store,
49
+ isCompactionInProgress: deps.lifecycle.isCompactionInProgress,
50
+ hasActiveTurn: deps.activeTurn.has,
51
+ hasDispatchPending: deps.lifecycle.hasDispatchPending,
52
+ isIdle: deps.isIdle,
53
+ hasPendingMessages: deps.hasPendingMessages,
54
+ hasDispatchContext: deps.deferredDispatch.isBound,
55
+ getDispatchGeneration: deps.deferredDispatch.getGeneration,
56
+ isDispatchGenerationActive: deps.deferredDispatch.isGenerationActive,
57
+ isQueueItemTransportActive(item) {
58
+ return deps.transportStamp.isActive(item.transportStamp);
59
+ },
60
+ hasPendingInboundQueueMutationForItem(item) {
61
+ return deps.admission.hasPendingQueueMutationForItem(item);
62
+ },
63
+ isQueueItemAdmissionReady(item) {
64
+ return (deps.admission.getSettlement()?.isItemReady(item) ??
65
+ (item.admissionReceipts?.length ?? 0) === 0);
66
+ },
67
+ commitPromptDispatch(item, ctx) {
68
+ if ((item.admissionReceipts?.length ?? 0) === 0)
69
+ return true;
70
+ const settlement = deps.admission.getSettlement();
71
+ if (!settlement?.onPromptHandedOff)
72
+ return false;
73
+ settlement.onPromptHandedOff(item, ctx);
74
+ return !settlement.isItemReady(item);
75
+ },
76
+ onControlSettled(item, ctx) {
77
+ deps.admission.getSettlement()?.onControlSettled(item, ctx);
78
+ },
79
+ onPromptSkipped(item, ctx) {
80
+ return settleDiscardedItems([item], ctx);
81
+ },
82
+ updateStatus: deps.updateStatus,
83
+ sendTextReply: deps.sendTextReply,
84
+ recordRuntimeEvent: deps.recordRuntimeEvent,
85
+ ...deps.promptDispatch,
86
+ sendUserMessage: deps.sendUserMessage,
87
+ });
88
+ return {
89
+ mutation,
90
+ dispatchNext: dispatch.dispatchNext,
91
+ requestNextDispatchAnnouncement: dispatch.requestNextDispatchAnnouncement,
92
+ watchdog: Queue.createTelegramQueueDispatchWatchdogRuntime({
93
+ hasQueuedItems: deps.store.hasQueuedItems,
94
+ dispatchNextQueuedTelegramTurn: dispatch.dispatchNext,
95
+ recordRuntimeEvent: deps.recordRuntimeEvent,
96
+ }),
97
+ };
98
+ }
99
+ export function createTelegramGenerativeAppLiveSurfaceBinding() {
100
+ let current;
101
+ return {
102
+ get() { return current; },
103
+ set(runtime) {
104
+ current?.shutdown();
105
+ current = runtime;
106
+ },
107
+ shutdown() {
108
+ current?.shutdown();
109
+ current = undefined;
110
+ },
111
+ };
112
+ }
113
+ export function createTelegramGenerativeAppBoundButtonActionInvoker(deps) {
114
+ return async (action, query) => {
115
+ let boundAction;
116
+ let handedOff;
117
+ try {
118
+ boundAction = GenerativeApps.parseGenerativeAppBoundAction(action.prompt);
119
+ if (!boundAction)
120
+ return false;
121
+ deps.assertExecutionCurrent(query);
122
+ const chatId = query.message?.chat?.id;
123
+ const messageId = query.message?.message_id;
124
+ if (typeof chatId !== "number" || typeof messageId !== "number") {
125
+ throw new Error("Generative App callback target is unavailable.");
126
+ }
127
+ const liveSurfaces = deps.getLiveSurfaceRuntime?.();
128
+ handedOff = liveSurfaces?.take(GenerativeApps.getTelegramBindLiveSurfaceKey(boundAction.app, deps.getActiveProfileName?.() ?? "default", {
129
+ chatId,
130
+ ...(query.message?.message_thread_id !== undefined
131
+ ? { threadId: query.message.message_thread_id }
132
+ : {}),
133
+ }));
134
+ const result = await GenerativeApps.invokeGenerativeApp({
135
+ agentDir: deps.agentDir,
136
+ ...(deps.getExecutionFence(query)
137
+ ? { execution: deps.getExecutionFence(query) }
138
+ : {}),
139
+ ...(boundAction.argument !== undefined
140
+ ? { argument: boundAction.argument }
141
+ : {}),
142
+ ...(action.binding?.app === boundAction.app
143
+ ? {
144
+ expectedGeneration: action.binding.generation,
145
+ expectedRevision: action.binding.revision,
146
+ }
147
+ : {}),
148
+ method: boundAction.method,
149
+ app: boundAction.app,
150
+ });
151
+ deps.assertExecutionCurrent(query);
152
+ const reply = deps.planOutput(result.output, {
153
+ binding: {
154
+ generation: result.generation,
155
+ app: result.app,
156
+ revision: result.revision,
157
+ },
158
+ });
159
+ if (handedOff && liveSurfaces) {
160
+ try {
161
+ await liveSurfaces.resume(handedOff, result);
162
+ deps.assertExecutionCurrent(query);
163
+ return "edit";
164
+ }
165
+ catch (error) {
166
+ deps.recordRuntimeEvent("generative-app", error, {
167
+ phase: "bound-action-live-edit-fallback",
168
+ app: boundAction.app,
169
+ method: boundAction.method,
170
+ });
171
+ }
172
+ }
173
+ if (result.viewMode === "edit" && deps.editInteractiveMessage) {
174
+ let editFailed = false;
175
+ try {
176
+ await deps.editInteractiveMessage(chatId, messageId, reply.markdown, "markdown", reply.replyMarkup ?? { inline_keyboard: [] });
177
+ }
178
+ catch (error) {
179
+ editFailed = true;
180
+ deps.recordRuntimeEvent("generative-app", error, {
181
+ phase: "bound-action-edit-fallback",
182
+ app: boundAction.app,
183
+ method: boundAction.method,
184
+ });
185
+ }
186
+ deps.assertExecutionCurrent(query);
187
+ if (!editFailed)
188
+ return "edit";
189
+ }
190
+ deps.assertExecutionCurrent(query);
191
+ await deps.sendMarkdownReply(chatId, messageId, reply.markdown, {
192
+ replyMarkup: reply.replyMarkup,
193
+ });
194
+ deps.assertExecutionCurrent(query);
195
+ return "new";
196
+ }
197
+ catch (error) {
198
+ if (handedOff)
199
+ deps.getLiveSurfaceRuntime?.()?.open(handedOff);
200
+ deps.recordRuntimeEvent("generative-app", error, {
201
+ phase: "bound-action",
202
+ ...(boundAction
203
+ ? { app: boundAction.app, method: boundAction.method }
204
+ : {}),
205
+ });
206
+ throw error;
207
+ }
208
+ };
209
+ }
210
+ export function createTelegramAgentMessageToolRoutingRuntime(deps) {
211
+ return {
212
+ async resolveAgentTarget(selector) {
213
+ if (!deps.ownsLeader())
214
+ return deps.follower.resolveTarget(selector);
215
+ const target = deps.local.resolveTarget(selector, deps.getSourceTarget());
216
+ if (!target) {
217
+ throw new Error("Telegram agent target is unavailable, ambiguous, or not live.");
218
+ }
219
+ return target;
220
+ },
221
+ async routeAgentMessage(message) {
222
+ if (!deps.ownsLeader())
223
+ return deps.follower.routeMessage(message);
224
+ await deps.local.route({
225
+ sourceTarget: deps.getSourceTarget(),
226
+ sourceThreadName: deps.getSourceThreadName(),
227
+ message,
228
+ });
229
+ },
230
+ canSendDirect() {
231
+ return deps.ownsDirectDelivery() || deps.isFollowerRegistered();
232
+ },
233
+ };
234
+ }
235
+ export function createTelegramAssistantOutputBindingRuntime(deps) {
236
+ const authority = Routing.createTelegramAssistantOutputAuthorityRuntime(deps.authority);
237
+ const sendOutput = OutboundHandlers.createTelegramAssistantOutputSender(deps.sender);
238
+ const runtime = Activity.createTelegramAssistantOutputRuntime({
239
+ ...authority,
240
+ enqueue: deps.enqueue,
241
+ prepareSend: (event) => event.source === "telegram" ? deps.prepareTelegramPreview?.() : undefined,
242
+ async send(event, authority, isAuthorityActive) {
243
+ await deps.waitForActivityIdle?.();
244
+ if (!isAuthorityActive())
245
+ return;
246
+ await sendOutput(event, authority, isAuthorityActive);
247
+ },
248
+ recordFailure(event, error) {
249
+ deps.recordRuntimeEvent("proactive-push", error, {
250
+ activityId: event.activityId,
251
+ sequence: event.sequence,
252
+ placement: event.placement,
253
+ });
254
+ },
255
+ });
256
+ return {
257
+ runtime,
258
+ authority,
259
+ observeEvent(event) {
260
+ if (event.type === "assistant-segment")
261
+ runtime.accept(event);
262
+ },
263
+ };
264
+ }
265
+ /** Compose public activity fanout, verbosity, and assistant output ordering. */
266
+ export function createTelegramActivityBindingRuntime(deps) {
267
+ const publication = Activity.createTelegramActivityPublicationRuntime();
268
+ const assistantOutputBinding = createTelegramAssistantOutputBindingRuntime({
269
+ ...deps.assistantOutput,
270
+ enqueue: publication.enqueue,
271
+ });
272
+ const activityVerbosityRuntime = ActivityVerbosity.createTelegramActivityVerbosityRuntime({
273
+ ...deps.activityVerbosity,
274
+ enqueue: publication.enqueue,
275
+ captureAuthority: assistantOutputBinding.authority.captureAuthority,
276
+ isAuthorityActive: assistantOutputBinding.authority.isAuthorityActive,
277
+ recordFailure(operation, event, error) {
278
+ deps.assistantOutput.recordRuntimeEvent("activity", error, {
279
+ operation,
280
+ eventType: event.type,
281
+ activityId: event.activityId,
282
+ });
283
+ },
284
+ });
285
+ const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
286
+ generation: deps.generation,
287
+ observeEvent(event) {
288
+ assistantOutputBinding.observeEvent(event);
289
+ activityVerbosityRuntime.accept(event);
290
+ },
291
+ recordFailure(handlerId, event, error) {
292
+ deps.assistantOutput.recordRuntimeEvent("activity", error, {
293
+ handlerId,
294
+ eventType: event.type,
295
+ activityId: event.activityId,
296
+ });
297
+ },
298
+ });
299
+ return {
300
+ activityRuntime: {
301
+ ...activityRuntime,
302
+ onSessionStart() {
303
+ publication.reset();
304
+ activityRuntime.onSessionStart?.();
305
+ },
306
+ onSessionShutdown() {
307
+ publication.reset();
308
+ activityRuntime.onSessionShutdown();
309
+ },
310
+ },
311
+ activityVerbosityRuntime,
312
+ assistantOutputRuntime: assistantOutputBinding.runtime,
313
+ publicationRuntime: {
314
+ enqueue: publication.enqueue,
315
+ reserve: publication.reserve,
316
+ capture() {
317
+ const authority = assistantOutputBinding.authority.captureAuthority();
318
+ return {
319
+ target: authority.target ? { ...authority.target } : undefined,
320
+ isCurrent: () => assistantOutputBinding.authority.isAuthorityActive(authority),
321
+ };
322
+ },
323
+ },
324
+ };
325
+ }
326
+ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, recoverPollingStart, getDisconnectThreadName, setRequestedThreadNameForPollingStart, validateThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, }) {
327
+ GenerativeApps.registerTelegramBindTool(pi, {
328
+ agentDir,
329
+ getActiveProfileName: configStore.getActiveProfileName,
330
+ getActiveTurn: activeTurnRuntime.get,
331
+ ...(setGenerativeAppLiveSurfaceRuntime
332
+ ? { setLiveSurfaceRuntime: setGenerativeAppLiveSurfaceRuntime }
333
+ : {}),
334
+ isDeliveryHandleCurrent: Delivery.isTelegramDeliveryHandleCurrent,
335
+ editView: (handle, view) => Delivery.editTelegramView(handle, view),
336
+ planOutput: OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore, Config.createTelegramConfigControls(configStore).getAssistantRenderingMode),
337
+ sendMarkdownReply,
338
+ sendView: (view, options) => Delivery.sendTelegramView(view, options),
339
+ recordRuntimeEvent,
340
+ });
341
+ ChannelPosts.registerTelegramChannelPostMutationTool(pi, { mutate: mutateChannelPost });
342
+ ChannelPosts.registerTelegramChannelPostListTool(pi, { list: listChannelPosts });
343
+ OutboundAttachments.registerTelegramOutboundAttachmentTool(pi, {
344
+ getActiveTurn: activeTurnRuntime.get,
345
+ getDefaultChatId,
346
+ getDefaultTarget,
347
+ canSendDirect,
348
+ sendMultipart: callMultipart,
349
+ recordRuntimeEvent,
350
+ });
351
+ OutboundAttachments.registerTelegramOutboundMessageTool(pi, {
352
+ getDefaultChatId,
353
+ getDefaultTarget,
354
+ getActiveTurn: activeTurnRuntime.get,
355
+ resolveAgentTarget,
356
+ routeAgentMessage,
357
+ canSendDirect,
358
+ planMessage: OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore, Config.createTelegramConfigControls(configStore).getAssistantRenderingMode),
359
+ sendMarkdownMessage: (chatId, markdown, options) => sendMarkdownReply(chatId, undefined, markdown, options),
360
+ sendChannelMarkdownMessage,
361
+ sendChannelMediaMessage,
362
+ recordRuntimeEvent,
363
+ });
364
+ const queueAgentConnectionContext = (connected) => {
365
+ pi.sendMessage({
366
+ customType: "telegram-connection-state",
367
+ content: connected
368
+ ? Prompts.TELEGRAM_CONNECTED_CONTEXT_MESSAGE
369
+ : Prompts.TELEGRAM_DISCONNECTED_CONTEXT_MESSAGE,
370
+ display: false,
371
+ }, { deliverAs: "nextTurn" });
372
+ };
373
+ Commands.registerTelegramBridgeCommands(pi, {
374
+ promptForConfig: async (ctx, profileName) => {
375
+ const nextProfileName = profileName ?? undefined;
376
+ if (profileName && !Config.isValidTelegramProfileName(profileName)) {
377
+ ctx.ui.notify(`Invalid Telegram profile name: ${profileName}`, "error");
378
+ return;
379
+ }
380
+ const previousProfileName = configStore.getActiveProfileName();
381
+ let setupConfigStore = configStore;
382
+ let persistSetupConfig = persistConfig;
383
+ if (!profileName) {
384
+ if (previousProfileName !== nextProfileName) {
385
+ await (stopPolling ?? lockedPollingRuntime.stop)();
386
+ }
387
+ configStore.activateProfile(undefined);
388
+ await onTransportChanged?.();
389
+ }
390
+ else {
391
+ const storedConfig = configStore.getStoredConfig();
392
+ setupConfigStore = Config.createTelegramConfigStore({
393
+ initialConfig: {
394
+ ...storedConfig,
395
+ profiles: {
396
+ ...(storedConfig.profiles ?? {}),
397
+ [profileName]: storedConfig.profiles?.[profileName] ?? {
398
+ botToken: "",
399
+ },
400
+ },
401
+ },
402
+ });
403
+ setupConfigStore.activateProfile(profileName);
404
+ persistSetupConfig = async () => {
405
+ try {
406
+ if (previousProfileName !== profileName) {
407
+ await (stopPolling ?? lockedPollingRuntime.stop)();
408
+ }
409
+ const profile = Config.getTelegramProfileFields(setupConfigStore.get());
410
+ if (!profile) {
411
+ throw new Error(`Telegram profile "${profileName}" has no token.`);
412
+ }
413
+ await configStore.load();
414
+ const latestProfile = configStore.getStoredConfig().profiles?.[profileName];
415
+ configStore.setProfile(profileName, {
416
+ ...profile,
417
+ threadDisplayMode: latestProfile?.threadDisplayMode,
418
+ });
419
+ configStore.activateProfile(profileName);
420
+ await onTransportChanged?.();
421
+ await persistConfig(configStore.get());
422
+ }
423
+ catch (error) {
424
+ await configStore.load().catch(() => undefined);
425
+ configStore.activateProfile(previousProfileName);
426
+ await onTransportChanged?.();
427
+ throw error;
428
+ }
429
+ };
430
+ }
431
+ const runSetup = Setup.createTelegramSetupPromptRuntime({
432
+ getConfig: setupConfigStore.get,
433
+ setConfig: setupConfigStore.set,
434
+ setupGuard: setup,
435
+ getMe: TelegramApi.fetchTelegramBotIdentity,
436
+ resolveBotToken: (value) => Config.resolveTelegramBotToken(value, process.env),
437
+ describeBotToken: (value) => Config.getTelegramBotTokenDiagnostic(value, process.env),
438
+ persistConfig: persistSetupConfig,
439
+ startPolling: lockedPollingRuntime.start,
440
+ updateStatus,
441
+ recordRuntimeEvent,
442
+ });
443
+ const completion = await runSetup(ctx);
444
+ if (completion.status === "success") {
445
+ queueAgentConnectionContext(true);
446
+ if (profileName) {
447
+ ctx.ui.notify(`Profile "${profileName}" saved and connected.`, "info");
448
+ }
449
+ }
450
+ },
451
+ getStatusLines,
452
+ reloadConfig: configStore.load,
453
+ hasBotToken: configStore.hasBotToken,
454
+ getBotTokenDiagnostic: configStore.getBotTokenDiagnostic,
455
+ startPolling: async (ctx, options) => {
456
+ setRequestedThreadNameForPollingStart?.(options?.requestedThreadName);
457
+ try {
458
+ return await lockedPollingRuntime.start(ctx, options);
459
+ }
460
+ catch (error) {
461
+ recordRuntimeEvent("recovery", error, { phase: "polling-start" });
462
+ throw error;
463
+ }
464
+ finally {
465
+ setRequestedThreadNameForPollingStart?.(undefined);
466
+ }
467
+ },
468
+ stopPolling: stopPolling ?? lockedPollingRuntime.stop,
469
+ recoverPollingStart,
470
+ getDisconnectThreadName,
471
+ validateThreadName,
472
+ queueAgentConnectionContext,
473
+ updateStatus,
474
+ getProfileNames: () => Config.getTelegramProfileNames(configStore.getStoredConfig()),
475
+ activateDefaultProfileConfig: async () => {
476
+ const previousProfileName = configStore.getActiveProfileName();
477
+ await configStore.load();
478
+ if (previousProfileName) {
479
+ await (stopPolling ?? lockedPollingRuntime.stop)();
480
+ }
481
+ configStore.activateProfile(undefined);
482
+ await onTransportChanged?.();
483
+ },
484
+ activateProfileConfig: async (_ctx, profileName) => {
485
+ const previousProfileName = configStore.getActiveProfileName();
486
+ await configStore.load();
487
+ if (!Config.isValidTelegramProfileName(profileName))
488
+ return false;
489
+ const storedConfig = configStore.getStoredConfig();
490
+ if (!storedConfig.profiles?.[profileName])
491
+ return false;
492
+ if (previousProfileName !== profileName) {
493
+ await (stopPolling ?? lockedPollingRuntime.stop)();
494
+ }
495
+ if (!configStore.activateProfile(profileName))
496
+ return false;
497
+ await onTransportChanged?.();
498
+ return true;
499
+ },
500
+ });
501
+ }
502
+ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, }) {
503
+ const agentEndResetter = Runtime.createTelegramAgentEndResetter({
504
+ abort,
505
+ typing,
506
+ clearActiveTurn: activeTurnRuntime.clear,
507
+ resetToolExecutions: lifecycle.resetActiveToolExecutions,
508
+ clearPendingModelSwitch: modelSwitchController.clearPendingSwitch,
509
+ clearDispatchPending: lifecycle.clearDispatchPending,
510
+ });
511
+ const queuedAttachmentSender = OutboundAttachments.createTelegramQueuedOutboundAttachmentSender({
512
+ sendMultipart: callMultipart,
513
+ sendTextReply,
514
+ recordRuntimeEvent,
515
+ });
516
+ const richAttachmentSender = OutboundAttachments.createTelegramRichOutboundAttachmentSender({
517
+ sendMultipart: callMultipart,
518
+ getRenderingMode: getAssistantRenderingMode,
519
+ recordOwnership: recordMessageOwnership,
520
+ recordRuntimeEvent,
521
+ });
522
+ const sendGuestAttachment = async (turn, attachment, caption) => {
523
+ const stagingTarget = proactivePushTargetGetter();
524
+ const stagingChatId = stagingTarget?.chatId;
525
+ if (stagingChatId === undefined) {
526
+ throw new Error("Guest attachment staging requires a paired Telegram chat");
527
+ }
528
+ await OutboundAttachments.deliverTelegramGuestCachedAttachment({
529
+ guestQueryId: turn.guestQueryId,
530
+ stagingChatId,
531
+ stagingTarget,
532
+ attachment,
533
+ caption,
534
+ sendMultipart: callMultipart,
535
+ answerGuestQuery: (guestQueryId, result) => answerGuestQuery(guestQueryId, undefined, { result }),
536
+ answerGuestText: (guestQueryId, text) => answerGuestQuery(guestQueryId, text),
537
+ fallbackText: caption ||
538
+ "Telegram bridge could not deliver the requested attachment.",
539
+ deleteMessage,
540
+ recordRuntimeEvent,
541
+ });
542
+ };
543
+ const outboundReplyPlanner = OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore, getAssistantRenderingMode);
544
+ const voiceReplySenderDeps = {
545
+ execCommand: CommandTemplates.execCommandTemplate,
546
+ sendMultipart: callMultipart,
547
+ sendTextReply,
548
+ sendChatAction,
549
+ sendRecordVoiceAction,
550
+ getHandlers: configStore.getOutboundHandlers,
551
+ recordRuntimeEvent,
552
+ };
553
+ const outboundReplyArtifactSender = OutboundHandlers.createTelegramOutboundReplyArtifactSender(voiceReplySenderDeps);
554
+ const sendGuestVoiceReply = async (turn, plan, caption) => {
555
+ const stagingTarget = proactivePushTargetGetter();
556
+ const stagingChatId = stagingTarget?.chatId;
557
+ if (stagingChatId === undefined) {
558
+ throw new Error("Guest voice staging requires a paired Telegram chat");
559
+ }
560
+ const guestVoiceSender = OutboundHandlers.createTelegramOutboundReplyArtifactSender({
561
+ ...voiceReplySenderDeps,
562
+ sendChatAction: undefined,
563
+ sendRecordVoiceAction: undefined,
564
+ sendMultipart: async (_method, _fields, _fileField, filePath, fileName) => {
565
+ try {
566
+ await OutboundAttachments.deliverTelegramGuestCachedAttachment({
567
+ guestQueryId: turn.guestQueryId,
568
+ stagingChatId,
569
+ stagingTarget,
570
+ attachment: { path: filePath, fileName },
571
+ caption,
572
+ sendMultipart: callMultipart,
573
+ answerGuestQuery: (guestQueryId, result) => answerGuestQuery(guestQueryId, undefined, { result }),
574
+ answerGuestText: (guestQueryId, text) => answerGuestQuery(guestQueryId, text),
575
+ fallbackText: caption || "Telegram bridge could not deliver the voice reply.",
576
+ deleteMessage,
577
+ recordRuntimeEvent,
578
+ });
579
+ }
580
+ catch (error) {
581
+ recordRuntimeEvent("delivery", error, {
582
+ phase: "guest-voice-answer",
583
+ guestQueryId: turn.guestQueryId,
584
+ });
585
+ }
586
+ return {};
587
+ },
588
+ });
589
+ await guestVoiceSender(turn, {
590
+ ...plan,
591
+ ...(plan.voiceReplies?.length
592
+ ? { voiceReplies: [plan.voiceReplies[0]] }
593
+ : {}),
594
+ }, { replyToPrompt: false });
595
+ };
596
+ let pendingFinalPublication;
597
+ const cancelPendingFinalPublication = () => {
598
+ pendingFinalPublication?.reservation.cancel();
599
+ pendingFinalPublication = undefined;
600
+ };
601
+ const recordPublicationFailure = (error) => {
602
+ recordRuntimeEvent("delivery", error, { phase: "agent-end-background-delivery" });
603
+ };
604
+ const scheduleActiveTurnDelivery = (task) => {
605
+ void publicationRuntime.enqueue(task).catch(recordPublicationFailure);
606
+ };
607
+ const agentLifecycleHooks = Queue.createTelegramAgentLifecycleHooks({
608
+ setAbortHandler: Runtime.createTelegramContextAbortHandlerSetter(abort),
609
+ getQueuedItems: telegramQueueStore.getQueuedItems,
610
+ hasPendingDispatch: lifecycle.hasDispatchPending,
611
+ hasActiveTurn: activeTurnRuntime.has,
612
+ resetToolExecutions: lifecycle.resetActiveToolExecutions,
613
+ resetPendingModelSwitch: modelSwitchController.clearPendingSwitch,
614
+ setQueuedItems: telegramQueueStore.setQueuedItems,
615
+ clearDispatchPending: lifecycle.clearDispatchPending,
616
+ setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
617
+ setActiveTurn: activeTurnRuntime.set,
618
+ onPromptHandedOff,
619
+ createPreviewState: previewRuntime.resetState,
620
+ startTypingLoop: (ctx) => {
621
+ const turn = activeTurnRuntime.get();
622
+ promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId, {
623
+ target: turn?.target,
624
+ });
625
+ },
626
+ updateStatus,
627
+ getActiveTurn: activeTurnRuntime.get,
628
+ loadConfig: configStore.load,
629
+ extractAssistant: Replies.extractRunAssistantMessage,
630
+ getFoldQueuedPromptsIntoHistory: lifecycle.shouldFoldQueuedPromptsIntoHistory,
631
+ resetRuntimeState: agentEndResetter,
632
+ isSessionActive: isSessionContextActive,
633
+ isTurnTransportActive,
634
+ waitForTypingIdle: typing.waitForIdle,
635
+ dispatchNextQueuedTelegramTurn,
636
+ requestDeferredDispatchNextQueuedTelegramTurn: deferredQueueDispatchRuntime.request,
637
+ scheduleActiveTurnDelivery,
638
+ reserveActiveTurnDelivery() {
639
+ const pending = pendingFinalPublication;
640
+ pendingFinalPublication = undefined;
641
+ const matches = pending?.turn === activeTurnRuntime.get();
642
+ if (!matches)
643
+ pending?.reservation.cancel();
644
+ const reservation = matches && pending ? pending.reservation : publicationRuntime.reserve();
645
+ return {
646
+ schedule: (task) => { void reservation.publish(task).catch(recordPublicationFailure); },
647
+ cancel: reservation.cancel,
648
+ };
649
+ },
650
+ preparePreviewDelivery,
651
+ preparePreviewClear: previewRuntime.prepareClear,
652
+ clearPreview: previewRuntime.clear,
653
+ setPreviewPendingText: previewRuntime.setPendingText,
654
+ finalizeMarkdownPreview,
655
+ sendMarkdownReply,
656
+ sendTextReply,
657
+ sendQueuedAttachments: queuedAttachmentSender,
658
+ sendRichAttachmentReply: richAttachmentSender,
659
+ answerGuestQuery,
660
+ sendGuestReply,
661
+ editGuestReply,
662
+ stopGuestPlaceholder,
663
+ sendGuestAttachment,
664
+ sendGuestVoiceReply,
665
+ planOutboundReply: outboundReplyPlanner,
666
+ sendOutboundReplyArtifacts: outboundReplyArtifactSender,
667
+ recordRuntimeEvent,
668
+ getActiveToolExecutions: lifecycle.getActiveToolExecutions,
669
+ setActiveToolExecutions: lifecycle.setActiveToolExecutions,
670
+ triggerPendingModelSwitchAbort: modelSwitchController.triggerPendingAbort,
671
+ });
672
+ Lifecycle.setResetTransportReplyDedup(Replies.resetTransportReplyDedup);
673
+ const agentStartWithDedupReset = Lifecycle.createAgentStartDedupHook(agentLifecycleHooks.onAgentStart, scheduleActiveTurnDelivery);
674
+ let uiPromptActive = false;
675
+ const startAgentActivityTypingLoop = (ctx) => {
676
+ if (uiPromptActive || !canSendAgentActivity(ctx))
677
+ return false;
678
+ const turn = activeTurnRuntime.get();
679
+ const target = turn?.target ?? proactivePushTargetGetter();
680
+ promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId ?? target?.chatId, {
681
+ target,
682
+ });
683
+ return true;
684
+ };
685
+ const startActiveTurnTypingLoop = (ctx) => {
686
+ if (uiPromptActive)
687
+ return;
688
+ const turn = activeTurnRuntime.get();
689
+ promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId, {
690
+ target: turn?.target,
691
+ });
692
+ };
693
+ let observedAutomaticCompaction = false;
694
+ let agentWorkActive = false;
695
+ const prepareCompactionNotice = (text, ctx) => {
696
+ const authority = publicationRuntime.capture();
697
+ const turn = activeTurnRuntime.get();
698
+ const selectedTarget = turn?.target ?? authority.target;
699
+ const target = selectedTarget ? { ...selectedTarget } : undefined;
700
+ const replyToMessageId = turn?.replyToMessageId;
701
+ return async () => {
702
+ if (!target || !isSessionContextActive(ctx) || !authority.isCurrent())
703
+ return;
704
+ if (turn && isTurnTransportActive?.(turn) === false)
705
+ return;
706
+ try {
707
+ await sendMarkdownReply(target.chatId, replyToMessageId, text, { target });
708
+ }
709
+ catch (error) {
710
+ recordRuntimeEvent("delivery", error, { phase: "compaction-notice" });
711
+ }
712
+ };
713
+ };
714
+ const sendCompactionNotice = (text, ctx) => {
715
+ scheduleActiveTurnDelivery(prepareCompactionNotice(text, ctx));
716
+ };
717
+ const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
718
+ isContextActive: isSessionContextActive,
719
+ setCompactionInProgress: lifecycle.setCompactionInProgress,
720
+ updateStatus,
721
+ startTypingLoop: startAgentActivityTypingLoop,
722
+ stopTypingLoop: typing.stop,
723
+ requestDeferredDispatchNextQueuedTelegramTurn: deferredQueueDispatchRuntime.request,
724
+ dispatchNextQueuedTelegramTurn,
725
+ recordRuntimeEvent,
726
+ onCompactionAbandoned: () => {
727
+ observedAutomaticCompaction = false;
728
+ activityRuntime.onCompactionAbandoned();
729
+ },
730
+ });
731
+ const messageActivityTypingHooks = Lifecycle.createTelegramMessageActivityTypingHooks({
732
+ hasActiveTurn: activeTurnRuntime.has,
733
+ startTypingLoop: startActiveTurnTypingLoop,
734
+ onMessageStart: previewRuntime.onMessageStart,
735
+ onMessageUpdate: previewRuntime.onMessageUpdate,
736
+ recordRuntimeEvent,
737
+ });
738
+ const messageActivityHooks = messageActivityTypingHooks;
739
+ Lifecycle.registerTelegramLifecycleHooks(pi, {
740
+ isSessionActive: isSessionContextActive,
741
+ ...sessionLifecycleRuntime,
742
+ ...agentLifecycleHooks,
743
+ onInput(event) {
744
+ activityRuntime.recordInputSource(event.source ?? "unknown");
745
+ },
746
+ async onSessionStart(event, ctx) {
747
+ cancelPendingFinalPublication();
748
+ previewRuntime.invalidate();
749
+ assistantOutputRuntime.start();
750
+ activityRuntime.onSessionStart?.();
751
+ activityVerbosityRuntime?.reset();
752
+ modelContextAvailabilityRuntime.reconcile();
753
+ await sessionLifecycleRuntime.onSessionStart(event, ctx);
754
+ },
755
+ async onSessionShutdown(event, ctx) {
756
+ if (!isSessionContextActive(ctx))
757
+ return;
758
+ shutdownGenerativeAppLiveSurfaces?.();
759
+ agentLifecycleHooks.clearRetainedAgentEnd();
760
+ activityRuntime.onSessionShutdown();
761
+ activityVerbosityRuntime?.reset();
762
+ assistantOutputRuntime.stop();
763
+ observedAutomaticCompaction = false;
764
+ agentWorkActive = false;
765
+ cancelPendingFinalPublication();
766
+ uiPromptActive = false;
767
+ compactionObserver.onSessionShutdown();
768
+ if (event.reason === "quit" && disconnectOnQuit) {
769
+ try {
770
+ const automaticCleanupEnabled = (await resolveAutomaticThreadCleanupEnabled?.()) ?? true;
771
+ if (automaticCleanupEnabled)
772
+ await disconnectOnQuit();
773
+ }
774
+ catch (error) {
775
+ recordRuntimeEvent("session", error, {
776
+ phase: "automatic-disconnect-on-quit",
777
+ });
778
+ }
779
+ }
780
+ await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
781
+ },
782
+ async onSessionBeforeCompact(event, ctx) {
783
+ if (!isSessionContextActive(ctx))
784
+ return;
785
+ const shouldNotify = !(lifecycle.isCompactionInProgress?.() ?? false);
786
+ if (shouldNotify)
787
+ observedAutomaticCompaction = true;
788
+ activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
789
+ compactionObserver.onSessionBeforeCompact(event, ctx);
790
+ if (shouldNotify)
791
+ sendCompactionNotice(Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN, ctx);
792
+ },
793
+ async onSessionCompact(event, ctx) {
794
+ if (!isSessionContextActive(ctx))
795
+ return;
796
+ activityRuntime.onCompactionEnd(Pi.getSessionCompactionReason(event));
797
+ compactionObserver.onSessionCompact(event, ctx);
798
+ if (observedAutomaticCompaction) {
799
+ observedAutomaticCompaction = false;
800
+ sendCompactionNotice(Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN, ctx);
801
+ }
802
+ },
803
+ async onSessionCompactFailed(event, ctx) {
804
+ if (!isSessionContextActive(ctx))
805
+ return;
806
+ const shouldNotify = observedAutomaticCompaction;
807
+ compactionObserver.onSessionCompactFailed(event, ctx);
808
+ if (!shouldNotify)
809
+ return;
810
+ const notice = event.aborted
811
+ ? "**⚠️ Compaction cancelled.**"
812
+ : "**⚠️ Compaction failed.**";
813
+ sendCompactionNotice(notice, ctx);
814
+ },
815
+ async onAgentStart(event, ctx) {
816
+ if (!isSessionContextActive(ctx))
817
+ return;
818
+ agentWorkActive = true;
819
+ cancelPendingFinalPublication();
820
+ await agentStartWithDedupReset(event, ctx);
821
+ const turn = activeTurnRuntime.get();
822
+ activityRuntime.onAgentStart(turn?.target, turn?.replyToMessageId);
823
+ startAgentActivityTypingLoop(ctx);
824
+ },
825
+ async onToolExecutionStart(event, ctx) {
826
+ if (!isSessionContextActive(ctx))
827
+ return;
828
+ agentLifecycleHooks.onToolExecutionStart();
829
+ activityRuntime.onToolStart({
830
+ toolCallId: event.toolCallId,
831
+ toolName: event.toolName,
832
+ args: event.args,
833
+ });
834
+ },
835
+ onToolExecutionUpdate(event, ctx) {
836
+ if (!isSessionContextActive(ctx))
837
+ return;
838
+ activityRuntime.onToolUpdate({
839
+ toolCallId: event.toolCallId,
840
+ toolName: event.toolName,
841
+ update: event.partialResult,
842
+ });
843
+ },
844
+ async onToolExecutionEnd(event, ctx) {
845
+ if (!isSessionContextActive(ctx))
846
+ return;
847
+ activityRuntime.onToolEnd({
848
+ toolCallId: event.toolCallId,
849
+ toolName: event.toolName,
850
+ result: event.result,
851
+ isError: event.isError,
852
+ });
853
+ agentLifecycleHooks.onToolExecutionEnd(event, ctx);
854
+ },
855
+ async onMessageStart(event, ctx) {
856
+ if (!isSessionContextActive(ctx))
857
+ return;
858
+ await messageActivityHooks.onMessageStart(event, ctx);
859
+ },
860
+ async onMessageUpdate(event, ctx) {
861
+ if (!isSessionContextActive(ctx))
862
+ return;
863
+ if (event.assistantMessageEvent) {
864
+ activityRuntime.onAssistantEvent(event.assistantMessageEvent);
865
+ }
866
+ await messageActivityHooks.onMessageUpdate(event, ctx);
867
+ },
868
+ onMessageEnd(event, ctx) {
869
+ if (!isSessionContextActive(ctx))
870
+ return;
871
+ if (event.message.role === "assistant") {
872
+ previewRuntime.seal();
873
+ activityRuntime.onAssistantMessageEnd(event.message.stopReason);
874
+ }
875
+ if (event.message.role !== "assistant" || event.message.stopReason === "toolUse" || event.message.stopReason === "aborted")
876
+ return;
877
+ const turn = activeTurnRuntime.get();
878
+ if (!turn || turn.guestQueryId || pendingFinalPublication?.turn === turn)
879
+ return;
880
+ cancelPendingFinalPublication();
881
+ const assistant = Replies.extractLatestAssistantMessageText([event.message]);
882
+ if (!assistant.text && assistant.stopReason !== "error" && turn.queuedAttachments.length === 0)
883
+ return;
884
+ pendingFinalPublication = { turn, reservation: publicationRuntime.reserve() };
885
+ },
886
+ onUiPromptStart(event, ctx) {
887
+ if (!isSessionContextActive(ctx))
888
+ return;
889
+ uiPromptActive = true;
890
+ typing.stop();
891
+ activityRuntime.onUiPromptStart(event.kind, event.title);
892
+ updateStatus(ctx);
893
+ },
894
+ onUiPromptEnd(_event, ctx) {
895
+ if (!isSessionContextActive(ctx))
896
+ return;
897
+ uiPromptActive = false;
898
+ activityRuntime.onUiPromptEnd();
899
+ if (agentWorkActive || lifecycle.isCompactionInProgress()) {
900
+ startAgentActivityTypingLoop(ctx);
901
+ }
902
+ updateStatus(ctx);
903
+ },
904
+ async onAgentEnd(event, ctx) {
905
+ if (!isSessionContextActive(ctx))
906
+ return;
907
+ if (pendingFinalPublication && pendingFinalPublication.turn !== activeTurnRuntime.get()) {
908
+ cancelPendingFinalPublication();
909
+ return;
910
+ }
911
+ activityRuntime.onAgentEnd();
912
+ await agentLifecycleHooks.onAgentEnd(event, ctx);
913
+ },
914
+ async onAgentSettled(event, ctx) {
915
+ if (!isSessionContextActive(ctx))
916
+ return;
917
+ const pending = pendingFinalPublication;
918
+ try {
919
+ await agentLifecycleHooks.onAgentSettled(event, ctx);
920
+ }
921
+ finally {
922
+ if (pendingFinalPublication === pending)
923
+ cancelPendingFinalPublication();
924
+ }
925
+ if (!isSessionContextActive(ctx))
926
+ return;
927
+ agentWorkActive = false;
928
+ activityRuntime.onAgentSettled();
929
+ modelContextAvailabilityRuntime.reconcile();
930
+ },
931
+ onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
932
+ reconcileAvailability: modelContextAvailabilityRuntime.reconcile,
933
+ isAvailable: canSendAgentActivity,
934
+ }),
935
+ });
936
+ }