@llblab/pi-telegram 0.11.2 → 0.13.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 (53) hide show
  1. package/AGENTS.md +20 -15
  2. package/BACKLOG.md +1 -11
  3. package/CHANGELOG.md +41 -1
  4. package/README.md +15 -41
  5. package/api/inbound.ts +14 -0
  6. package/api/keyboard.ts +10 -0
  7. package/api/outbound.ts +11 -0
  8. package/api/sections.ts +17 -0
  9. package/api/updates.ts +11 -0
  10. package/api/voice.ts +24 -0
  11. package/docs/README.md +7 -5
  12. package/docs/architecture.md +162 -226
  13. package/docs/callback-namespaces.md +3 -3
  14. package/docs/command-templates.md +18 -16
  15. package/docs/{inbound-handlers.md → inbound.md} +14 -11
  16. package/docs/locks.md +3 -3
  17. package/docs/{outbound-handlers.md → outbound.md} +14 -11
  18. package/docs/public-api.md +420 -0
  19. package/docs/{extension-sections.md → sections.md} +34 -30
  20. package/docs/ui-style.md +165 -0
  21. package/docs/{external-handlers.md → updates.md} +33 -31
  22. package/docs/voice.md +27 -19
  23. package/index.ts +88 -242
  24. package/lib/bindings.ts +299 -0
  25. package/lib/command-templates.ts +249 -60
  26. package/lib/commands.ts +114 -1
  27. package/lib/config.ts +44 -4
  28. package/lib/{inbound-handlers.ts → inbound.ts} +31 -21
  29. package/lib/lifecycle.ts +41 -6
  30. package/lib/locks.ts +4 -1
  31. package/lib/menu-model.ts +3 -3
  32. package/lib/menu-queue.ts +1 -1
  33. package/lib/menu-settings.ts +21 -10
  34. package/lib/menu-status.ts +1 -1
  35. package/lib/menu.ts +1 -1
  36. package/lib/outbound-buttons.ts +226 -0
  37. package/lib/outbound-markup.ts +357 -0
  38. package/lib/outbound-voice.ts +263 -0
  39. package/lib/outbound.ts +908 -0
  40. package/lib/polling.ts +4 -3
  41. package/lib/preview.ts +2 -2
  42. package/lib/queue.ts +3 -0
  43. package/lib/replies.ts +4 -1
  44. package/lib/routing.ts +44 -3
  45. package/lib/{extension-sections.ts → sections.ts} +37 -8
  46. package/lib/status.ts +13 -0
  47. package/lib/{api.ts → telegram-api.ts} +4 -4
  48. package/lib/text-groups.ts +3 -2
  49. package/lib/updates.ts +121 -1
  50. package/lib/voice.ts +67 -21
  51. package/package.json +13 -3
  52. package/lib/external-handlers.ts +0 -166
  53. package/lib/outbound-handlers.ts +0 -1663
@@ -0,0 +1,299 @@
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
+
7
+ import * as CommandTemplates from "./command-templates.ts";
8
+ import * as Commands from "./commands.ts";
9
+ import * as Config from "./config.ts";
10
+ import * as Keyboard from "./keyboard.ts";
11
+ import * as Lifecycle from "./lifecycle.ts";
12
+ import * as Locks from "./locks.ts";
13
+ import * as Model from "./model.ts";
14
+ import * as OutboundAttachments from "./outbound-attachments.ts";
15
+ import * as OutboundHandlers from "./outbound.ts";
16
+ import * as Pi from "./pi.ts";
17
+ import * as Preview from "./preview.ts";
18
+ import * as Prompts from "./prompts.ts";
19
+ import * as Queue from "./queue.ts";
20
+ import * as Replies from "./replies.ts";
21
+ import * as Runtime from "./runtime.ts";
22
+ import * as Setup from "./setup.ts";
23
+ import * as Status from "./status.ts";
24
+ import * as TelegramApi from "./telegram-api.ts";
25
+
26
+ type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
27
+
28
+ type TelegramRuntimeEventRecorder = (
29
+ category: string,
30
+ error: unknown,
31
+ details?: Record<string, unknown>,
32
+ ) => void;
33
+
34
+ type TelegramBridgeStatusUpdater =
35
+ Status.TelegramStatusRuntime<Pi.ExtensionContext>["updateStatus"];
36
+
37
+ interface TelegramCommandsAndToolsBindingDeps {
38
+ pi: Pi.ExtensionAPI;
39
+ configStore: Config.TelegramConfigStore;
40
+ setup: Setup.TelegramSetupGuard;
41
+ activeTurnRuntime: Queue.TelegramActiveTurnStore<Queue.PendingTelegramTurn>;
42
+ lockedPollingRuntime: Locks.TelegramLockedPollingRuntime<Pi.ExtensionContext>;
43
+ getStatusLines: () => string[];
44
+ updateStatus: TelegramBridgeStatusUpdater;
45
+ recordRuntimeEvent: TelegramRuntimeEventRecorder;
46
+ }
47
+
48
+ export function registerTelegramCommandsAndTools({
49
+ pi,
50
+ configStore,
51
+ setup,
52
+ activeTurnRuntime,
53
+ lockedPollingRuntime,
54
+ getStatusLines,
55
+ updateStatus,
56
+ recordRuntimeEvent,
57
+ }: TelegramCommandsAndToolsBindingDeps): void {
58
+ OutboundAttachments.registerTelegramOutboundAttachmentTool(pi, {
59
+ getActiveTurn: activeTurnRuntime.get,
60
+ recordRuntimeEvent,
61
+ });
62
+ Commands.registerTelegramBridgeCommands(pi, {
63
+ promptForConfig: Setup.createTelegramSetupPromptRuntime({
64
+ getConfig: configStore.get,
65
+ setConfig: configStore.set,
66
+ setupGuard: setup,
67
+ getMe: TelegramApi.fetchTelegramBotIdentity,
68
+ persistConfig: configStore.persist,
69
+ startPolling: lockedPollingRuntime.start,
70
+ updateStatus,
71
+ recordRuntimeEvent,
72
+ }),
73
+ getStatusLines,
74
+ reloadConfig: configStore.load,
75
+ hasBotToken: configStore.hasBotToken,
76
+ startPolling: lockedPollingRuntime.start,
77
+ stopPolling: lockedPollingRuntime.stop,
78
+ updateStatus,
79
+ });
80
+ }
81
+
82
+ interface TelegramLifecycleBindingDeps {
83
+ pi: Pi.ExtensionAPI;
84
+ sessionLifecycleRuntime: Pick<
85
+ Lifecycle.TelegramLifecycleRegistrationDeps,
86
+ "onSessionStart" | "onSessionShutdown" | "onModelSelect"
87
+ >;
88
+ queueSessionLifecycle: Pick<
89
+ Lifecycle.TelegramLifecycleRegistrationDeps,
90
+ "onSessionShutdown"
91
+ >;
92
+ configStore: Pick<Config.TelegramConfigStore, "getOutboundHandlers">;
93
+ abort: Runtime.TelegramRuntimeAbortPort;
94
+ typing: Runtime.TelegramRuntimeTypingPort;
95
+ lifecycle: Runtime.TelegramRuntimeLifecyclePort;
96
+ activeTurnRuntime: Queue.TelegramActiveTurnStore<Queue.PendingTelegramTurn>;
97
+ telegramQueueStore: Queue.TelegramQueueStore<Pi.ExtensionContext>;
98
+ modelSwitchController: Model.TelegramModelSwitchController<
99
+ Pi.ExtensionContext,
100
+ Model.ScopedTelegramModel<ActivePiModel>
101
+ >;
102
+ previewRuntime: Preview.TelegramAssistantPreviewRuntime<
103
+ Pi.AgentEndEvent["messages"][number],
104
+ Keyboard.TelegramInlineKeyboardMarkup
105
+ >;
106
+ promptDispatchRuntime: Runtime.TelegramPromptDispatchRuntime<Pi.ExtensionContext>;
107
+ deferredQueueDispatchRuntime: Queue.TelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>;
108
+ lockOwnershipGuard: Pick<
109
+ Locks.TelegramLockOwnershipGuard<Pi.ExtensionContext>,
110
+ "ownsContext"
111
+ >;
112
+ buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
113
+ callMultipart: OutboundHandlers.TelegramVoiceReplySenderDeps["sendMultipart"];
114
+ sendChatAction: NonNullable<
115
+ OutboundHandlers.TelegramVoiceReplySenderDeps["sendChatAction"]
116
+ >;
117
+ sendRecordVoiceAction: NonNullable<
118
+ OutboundHandlers.TelegramVoiceReplySenderDeps["sendRecordVoiceAction"]
119
+ >;
120
+ sendMarkdownReply: Queue.TelegramAgentEndHookRuntimeDeps<
121
+ Queue.PendingTelegramTurn,
122
+ Pi.ExtensionContext,
123
+ Pi.AgentEndEvent["messages"][number],
124
+ Keyboard.TelegramInlineKeyboardMarkup
125
+ >["sendMarkdownReply"];
126
+ sendTextReply: Queue.TelegramAgentEndHookRuntimeDeps<
127
+ Queue.PendingTelegramTurn,
128
+ Pi.ExtensionContext,
129
+ Pi.AgentEndEvent["messages"][number],
130
+ Keyboard.TelegramInlineKeyboardMarkup
131
+ >["sendTextReply"] &
132
+ NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendTextReply"]>;
133
+ dispatchNextQueuedTelegramTurn: (ctx: Pi.ExtensionContext) => void;
134
+ answerGuestQuery: NonNullable<
135
+ Queue.TelegramAgentEndHookRuntimeDeps<
136
+ Queue.PendingTelegramTurn,
137
+ Pi.ExtensionContext,
138
+ Pi.AgentEndEvent["messages"][number],
139
+ Keyboard.TelegramInlineKeyboardMarkup
140
+ >["answerGuestQuery"]
141
+ >;
142
+ sendGuestReply: NonNullable<
143
+ Queue.TelegramAgentEndHookRuntimeDeps<
144
+ Queue.PendingTelegramTurn,
145
+ Pi.ExtensionContext,
146
+ Pi.AgentEndEvent["messages"][number],
147
+ Keyboard.TelegramInlineKeyboardMarkup
148
+ >["sendGuestReply"]
149
+ >;
150
+ finalizeMarkdownPreview: Queue.TelegramAgentEndHookRuntimeDeps<
151
+ Queue.PendingTelegramTurn,
152
+ Pi.ExtensionContext,
153
+ Pi.AgentEndEvent["messages"][number],
154
+ Keyboard.TelegramInlineKeyboardMarkup
155
+ >["finalizeMarkdownPreview"];
156
+ proactivePushChatIdGetter: () => number | undefined;
157
+ isProactivePushEnabled: () => boolean;
158
+ updateStatus: TelegramBridgeStatusUpdater;
159
+ recordRuntimeEvent: TelegramRuntimeEventRecorder;
160
+ }
161
+
162
+ export function registerTelegramLifecycleRuntimeHooks({
163
+ pi,
164
+ sessionLifecycleRuntime,
165
+ queueSessionLifecycle,
166
+ configStore,
167
+ abort,
168
+ typing,
169
+ lifecycle,
170
+ activeTurnRuntime,
171
+ telegramQueueStore,
172
+ modelSwitchController,
173
+ previewRuntime,
174
+ promptDispatchRuntime,
175
+ deferredQueueDispatchRuntime,
176
+ lockOwnershipGuard,
177
+ buttonActionStore,
178
+ callMultipart,
179
+ sendChatAction,
180
+ sendRecordVoiceAction,
181
+ sendMarkdownReply,
182
+ sendTextReply,
183
+ dispatchNextQueuedTelegramTurn,
184
+ answerGuestQuery,
185
+ sendGuestReply,
186
+ finalizeMarkdownPreview,
187
+ proactivePushChatIdGetter,
188
+ isProactivePushEnabled,
189
+ updateStatus,
190
+ recordRuntimeEvent,
191
+ }: TelegramLifecycleBindingDeps): void {
192
+ const agentEndResetter = Runtime.createTelegramAgentEndResetter({
193
+ abort,
194
+ typing,
195
+ clearActiveTurn: activeTurnRuntime.clear,
196
+ resetToolExecutions: lifecycle.resetActiveToolExecutions,
197
+ clearPendingModelSwitch: modelSwitchController.clearPendingSwitch,
198
+ clearDispatchPending: lifecycle.clearDispatchPending,
199
+ });
200
+ const queuedAttachmentSender =
201
+ OutboundAttachments.createTelegramQueuedOutboundAttachmentSender({
202
+ sendMultipart: callMultipart,
203
+ sendTextReply,
204
+ recordRuntimeEvent,
205
+ });
206
+ const outboundReplyPlanner =
207
+ OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore);
208
+ const outboundReplyArtifactSender =
209
+ OutboundHandlers.createTelegramOutboundReplyArtifactSender({
210
+ execCommand: CommandTemplates.execCommandTemplate,
211
+ sendMultipart: callMultipart,
212
+ sendTextReply,
213
+ sendChatAction,
214
+ sendRecordVoiceAction,
215
+ getHandlers: configStore.getOutboundHandlers,
216
+ recordRuntimeEvent,
217
+ });
218
+ const agentLifecycleHooks = Queue.createTelegramAgentLifecycleHooks<
219
+ Queue.PendingTelegramTurn,
220
+ Pi.ExtensionContext,
221
+ unknown,
222
+ Keyboard.TelegramInlineKeyboardMarkup
223
+ >({
224
+ setAbortHandler: Runtime.createTelegramContextAbortHandlerSetter(abort),
225
+ getQueuedItems: telegramQueueStore.getQueuedItems,
226
+ hasPendingDispatch: lifecycle.hasDispatchPending,
227
+ hasActiveTurn: activeTurnRuntime.has,
228
+ resetToolExecutions: lifecycle.resetActiveToolExecutions,
229
+ resetPendingModelSwitch: modelSwitchController.clearPendingSwitch,
230
+ setQueuedItems: telegramQueueStore.setQueuedItems,
231
+ clearDispatchPending: lifecycle.clearDispatchPending,
232
+ setActiveTurn: activeTurnRuntime.set,
233
+ createPreviewState: previewRuntime.resetState,
234
+ startTypingLoop: promptDispatchRuntime.startTypingLoop,
235
+ updateStatus,
236
+ getActiveTurn: activeTurnRuntime.get,
237
+ extractAssistant: Replies.extractLatestAssistantMessageText,
238
+ getPreserveQueuedTurnsAsHistory:
239
+ lifecycle.shouldPreserveQueuedTurnsAsHistory,
240
+ resetRuntimeState: agentEndResetter,
241
+ dispatchNextQueuedTelegramTurn,
242
+ requestDeferredDispatchNextQueuedTelegramTurn:
243
+ deferredQueueDispatchRuntime.request,
244
+ clearPreview: previewRuntime.clear,
245
+ setPreviewPendingText: previewRuntime.setPendingText,
246
+ finalizeMarkdownPreview,
247
+ sendMarkdownReply,
248
+ sendTextReply,
249
+ sendQueuedAttachments: queuedAttachmentSender,
250
+ answerGuestQuery,
251
+ sendGuestReply,
252
+ planOutboundReply: outboundReplyPlanner,
253
+ sendOutboundReplyArtifacts: outboundReplyArtifactSender,
254
+ isCurrentOwner: lockOwnershipGuard.ownsContext,
255
+ getDefaultChatId: proactivePushChatIdGetter,
256
+ isProactivePushEnabled,
257
+ recordRuntimeEvent,
258
+ getActiveToolExecutions: lifecycle.getActiveToolExecutions,
259
+ setActiveToolExecutions: lifecycle.setActiveToolExecutions,
260
+ triggerPendingModelSwitchAbort: modelSwitchController.triggerPendingAbort,
261
+ });
262
+ Lifecycle.setResetTransportReplyDedup(Replies.resetTransportReplyDedup);
263
+ const agentStartWithDedupReset = Lifecycle.createAgentStartDedupHook(
264
+ agentLifecycleHooks.onAgentStart,
265
+ );
266
+ const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
267
+ setCompactionInProgress: lifecycle.setCompactionInProgress,
268
+ updateStatus,
269
+ startTypingLoop: promptDispatchRuntime.startTypingLoop,
270
+ stopTypingLoop: typing.stop,
271
+ requestDeferredDispatchNextQueuedTelegramTurn:
272
+ deferredQueueDispatchRuntime.request,
273
+ dispatchNextQueuedTelegramTurn,
274
+ recordRuntimeEvent,
275
+ });
276
+ const messageActivityTypingHooks =
277
+ Lifecycle.createTelegramMessageActivityTypingHooks({
278
+ hasActiveTurn: activeTurnRuntime.has,
279
+ startTypingLoop: promptDispatchRuntime.startTypingLoop,
280
+ onMessageStart: previewRuntime.onMessageStart,
281
+ onMessageUpdate: previewRuntime.onMessageUpdate,
282
+ });
283
+ Lifecycle.registerTelegramLifecycleHooks(pi, {
284
+ ...sessionLifecycleRuntime,
285
+ ...agentLifecycleHooks,
286
+ async onSessionShutdown(event, ctx) {
287
+ compactionObserver.onSessionShutdown();
288
+ await queueSessionLifecycle.onSessionShutdown(event, ctx);
289
+ },
290
+ onSessionBeforeCompact: compactionObserver.onSessionBeforeCompact,
291
+ onSessionCompact: compactionObserver.onSessionCompact,
292
+ onAgentStart: agentStartWithDedupReset,
293
+ onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
294
+ isProactivePushEnabled,
295
+ isCurrentOwner: lockOwnershipGuard.ownsContext,
296
+ }),
297
+ ...messageActivityTypingHooks,
298
+ });
299
+ }