@llblab/pi-telegram 0.10.0 → 0.10.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.2: Delete Message Port Hotfix
4
+
5
+ - `[ctx.deleteMessage()]` Added `deleteMessage()` to `TelegramSectionContext` and `TelegramSectionCallbackContext`. Extensions can now delete the message that triggered a callback — useful for cleaning up confirmation dialogs after the user makes a choice.
6
+ - `[API]` Added `deleteMessage` to `TelegramBridgeApiRuntime`, backed by Telegram's `deleteMessage` Bot API method with error recording.
7
+ - `[Demo]` Confirmation dialog in `pi-telegram-extension-demo` now deletes itself on answer (`ctx.deleteMessage()`) and posts a follow-up result message (`ctx.open()`).
8
+ - `[Docs]` Updated context port listings and interactive-messages section in `extension-sections.md` with `deleteMessage()`.
9
+
10
+ ## 0.10.1: Navigation Abstraction Hotfix
11
+
12
+ - `[ctx.open()]` Removed automatic Back-row prepend from `ctx.open()`. `ctx.open()` sends a new message into the chat — a Back button makes no sense outside the menu. `ctx.edit()` still auto-prepends the correct navigation row for in-menu views.
13
+ - `[Platform Docs]` Extended `docs/extension-sections.md` with a dedicated section on sending interactive messages into chat via `ctx.open()`: confirmation dialogs, approve/deny gates, and extension-driven button flows that live outside the menu hierarchy.
14
+
3
15
  ## 0.10.0: Extension Sections Platform
4
16
 
5
17
  - `[Extension Sections]` Implemented the Telegram Extension Sections platform: extensions can register structured UI sections that appear in the main Telegram application menu and Settings submenu without owning a second bot poller.
package/README.md CHANGED
@@ -209,7 +209,7 @@ Unknown inline-button callbacks are forwarded to π as `[callback] <data>` when
209
209
 
210
210
  Ordinary pi extensions can register structured UI sections that appear in the main Telegram menu and Settings submenu without owning a second poller. Each section gets a narrow typed context with `edit`, `open`, `enqueuePrompt`, `answerCallback`, and `callbackData()` — enough to build interactive Telegram-native surfaces while `pi-telegram` owns transport, callback routing, navigation hierarchy, and diagnostics.
211
211
 
212
- Import from `@llblab/pi-telegram`, call `registerTelegramSection()`, and return a disposer on shutdown. See [`@llblab/pi-telegram-extension-demo`](https://github.com/llblab/pi-telegram-extension-demo) for a working reference and the [Extension Sections Standard](./docs/extension-sections.md) for the full contract.
212
+ Import from `@llblab/pi-telegram`, call `registerTelegramSection()`, and return a disposer on shutdown. Sections can send interactive messages directly into the chat via `ctx.open()` — confirmation dialogs, approve/deny gates, and multi-step forms live outside the menu hierarchy while callbacks route through the same typed handler. See [`@llblab/pi-telegram-extension-demo`](https://github.com/llblab/pi-telegram-extension-demo) for a working reference and the [Extension Sections Standard](./docs/extension-sections.md) for the full contract.
213
213
 
214
214
  ### Proactive push
215
215
 
@@ -260,6 +260,8 @@ interface TelegramSectionContext {
260
260
  enqueuePrompt(prompt: string): Promise<void>;
261
261
  /** Build a section-namespaced callback_data string */
262
262
  callbackData(action: string, payload?: string): string;
263
+ /** Delete the message that triggered this callback */
264
+ deleteMessage(): Promise<void>;
263
265
  }
264
266
  ```
265
267
 
@@ -279,6 +281,8 @@ interface TelegramSectionCallbackContext {
279
281
  open(view: TelegramSectionView): Promise<void>;
280
282
  enqueuePrompt(prompt: string): Promise<void>;
281
283
  callbackData(action: string, payload?: string): string;
284
+ /** Delete the message that triggered this callback */
285
+ deleteMessage(): Promise<void>;
282
286
  }
283
287
  ```
284
288
 
@@ -298,6 +302,46 @@ Context ports are intentionally narrow. Sections **cannot**:
298
302
 
299
303
  Add capability-specific ports only when the first real extension proves the need.
300
304
 
305
+ ### Interactive messages in chat (`ctx.open`)
306
+
307
+ `ctx.open()` sends a new message directly into the Telegram chat — outside the menu hierarchy. No Back row is prepended. Use it for extension-driven interactions that live in the conversation:
308
+
309
+ - Confirmation dialogs ("Delete file.txt?")
310
+ - Approve/deny gates ("Allow tool execution?")
311
+ - Multi-step forms that should not be menu-bound
312
+ - Status reports with action buttons
313
+
314
+ ```ts
315
+ handleCallback: async (ctx) => {
316
+ if (ctx.action === "delete-file") {
317
+ await ctx.open({
318
+ text: `<b>Delete ${ctx.payload}?</b>\n\nThis cannot be undone.`,
319
+ parseMode: "html",
320
+ replyMarkup: {
321
+ inline_keyboard: [[
322
+ { text: "✅ Yes, delete",
323
+ callback_data: ctx.callbackData("confirm-delete", ctx.payload) },
324
+ { text: "❌ Cancel",
325
+ callback_data: ctx.callbackData("cancel") },
326
+ ]],
327
+ },
328
+ });
329
+ return "handled";
330
+ }
331
+ if (ctx.action === "confirm-delete") {
332
+ await ctx.deleteMessage();
333
+ await ctx.answerCallback(`Deleted: ${ctx.payload}`);
334
+ return "handled";
335
+ }
336
+ if (ctx.action === "cancel") {
337
+ await ctx.deleteMessage();
338
+ return "handled";
339
+ }
340
+ }
341
+ ```
342
+
343
+ `ctx.deleteMessage()` removes the dialog from chat after the user makes a choice. Callbacks from chat buttons route through the same `handleCallback` — the same `ctx.callbackData()` works regardless of where the button lives. The extension owns its callback namespace; the bridge owns transport.
344
+
301
345
  ## 10. Telegram Bot API Integration
302
346
 
303
347
  ### `callback_data` contract
package/index.ts CHANGED
@@ -157,6 +157,7 @@ export default function (pi: Pi.ExtensionAPI) {
157
157
  editMessageText: editTelegramMessageText,
158
158
  answerCallbackQuery,
159
159
  answerGuestQuery,
160
+ deleteMessage: deleteTelegramMessage,
160
161
  prepareTempDir,
161
162
  } = Api.createDefaultTelegramBridgeApiRuntime({
162
163
  getBotToken: configStore.getBotToken,
@@ -354,6 +355,7 @@ export default function (pi: Pi.ExtensionAPI) {
354
355
  answerCallbackQuery,
355
356
  editInteractiveMessage,
356
357
  sendInteractiveMessage,
358
+ deleteMessage: deleteTelegramMessage,
357
359
  answerGuestQuery,
358
360
  sendTextReply,
359
361
  setMyCommands,
package/lib/api.ts CHANGED
@@ -323,6 +323,7 @@ export interface TelegramBridgeApiRuntime {
323
323
  text?: string,
324
324
  options?: { parseMode?: string },
325
325
  ) => Promise<void>;
326
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
326
327
  prepareTempDir: () => Promise<number>;
327
328
  }
328
329
 
@@ -668,6 +669,21 @@ export async function answerTelegramCallbackQuery(
668
669
  }
669
670
  }
670
671
 
672
+ export async function deleteTelegramMessage(
673
+ botToken: string | undefined,
674
+ chatId: number,
675
+ messageId: number,
676
+ ): Promise<void> {
677
+ try {
678
+ await callTelegram<boolean>(botToken, "deleteMessage", {
679
+ chat_id: chatId,
680
+ message_id: messageId,
681
+ });
682
+ } catch {
683
+ // ignore
684
+ }
685
+ }
686
+
671
687
  export function createTelegramChatActionSender<TAction extends string>(
672
688
  sendChatAction: (chatId: number, action: TAction) => Promise<unknown>,
673
689
  action: TAction,
@@ -817,6 +833,11 @@ export function createTelegramBridgeApiRuntime(
817
833
  },
818
834
  prepareTempDir: () =>
819
835
  prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
836
+ deleteMessage: (chatId, messageId) =>
837
+ callRecorded<boolean>("deleteMessage", {
838
+ chat_id: chatId,
839
+ message_id: messageId,
840
+ }).then(() => {}),
820
841
  };
821
842
  }
822
843
 
@@ -52,6 +52,8 @@ export interface TelegramSectionContext {
52
52
  open(view: TelegramSectionView): Promise<void>;
53
53
  enqueuePrompt(prompt: string): Promise<void>;
54
54
  callbackData(action: string, payload?: string): string;
55
+ /** Delete the message that triggered this callback (dialog cleanup) */
56
+ deleteMessage(): Promise<void>;
55
57
  }
56
58
 
57
59
  export interface TelegramSectionCallbackContext {
@@ -65,6 +67,8 @@ export interface TelegramSectionCallbackContext {
65
67
  open(view: TelegramSectionView): Promise<void>;
66
68
  enqueuePrompt(prompt: string): Promise<void>;
67
69
  callbackData(action: string, payload?: string): string;
70
+ /** Delete the message that triggered this callback (dialog cleanup) */
71
+ deleteMessage(): Promise<void>;
68
72
  }
69
73
 
70
74
  export interface RegisteredTelegramSection {
@@ -121,6 +125,7 @@ export interface TelegramSectionRuntimeDeps {
121
125
  replyMarkup: TelegramInlineKeyboardMarkup,
122
126
  ) => Promise<number | undefined>;
123
127
  enqueuePrompt: (prompt: string) => Promise<void>;
128
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
124
129
  }
125
130
 
126
131
  function buildTelegramSectionContext(
@@ -157,7 +162,7 @@ function buildTelegramSectionContext(
157
162
  chatId,
158
163
  view.text,
159
164
  view.parseMode ?? "html",
160
- prependBackRow(view.replyMarkup, backCallback, backLabel),
165
+ view.replyMarkup ?? { inline_keyboard: [] },
161
166
  )
162
167
  .then(() => {}),
163
168
  enqueuePrompt: deps.enqueuePrompt,
@@ -165,6 +170,10 @@ function buildTelegramSectionContext(
165
170
  payload
166
171
  ? `section:${token}:${action}:${payload}`
167
172
  : `section:${token}:${action}`,
173
+ deleteMessage: () =>
174
+ messageId !== undefined
175
+ ? deps.deleteMessage(chatId, messageId)
176
+ : Promise.resolve(),
168
177
  };
169
178
  }
170
179
 
@@ -203,7 +212,7 @@ function buildTelegramSectionCallbackContext(
203
212
  chatId,
204
213
  view.text,
205
214
  view.parseMode ?? "html",
206
- prependBackRow(view.replyMarkup, backCallback, backLabel),
215
+ view.replyMarkup ?? { inline_keyboard: [] },
207
216
  )
208
217
  .then(() => {}),
209
218
  enqueuePrompt: deps.enqueuePrompt,
@@ -211,6 +220,10 @@ function buildTelegramSectionCallbackContext(
211
220
  payload
212
221
  ? `section:${token}:${action}:${payload}`
213
222
  : `section:${token}:${action}`,
223
+ deleteMessage: () =>
224
+ messageId !== undefined
225
+ ? deps.deleteMessage(chatId, messageId)
226
+ : Promise.resolve(),
214
227
  };
215
228
  }
216
229
 
@@ -402,6 +415,7 @@ export interface TelegramSectionCallbackHandlerDeps {
402
415
  replyMarkup: TelegramInlineKeyboardMarkup,
403
416
  ) => Promise<number | undefined>;
404
417
  enqueuePrompt: (prompt: string) => Promise<void>;
418
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
405
419
  }
406
420
 
407
421
  export async function handleTelegramSectionOpen(
package/lib/menu.ts CHANGED
@@ -238,6 +238,7 @@ export interface TelegramMenuCallbackRuntimeDeps<
238
238
  replyMarkup: TelegramReplyMarkup,
239
239
  ) => Promise<number | undefined>;
240
240
  enqueueSectionPrompt?: (prompt: string, ctx: TContext) => Promise<void>;
241
+ deleteMessage?: (chatId: number, messageId: number) => Promise<void>;
241
242
  }
242
243
 
243
244
  export interface TelegramMenuActionRuntimeDeps<
@@ -471,6 +472,7 @@ export interface TelegramMenuCallbackRuntimeAdapterDeps<
471
472
  replyMarkup: TelegramReplyMarkup,
472
473
  ) => Promise<number | undefined>;
473
474
  enqueueSectionPrompt?: (prompt: string, ctx: TContext) => Promise<void>;
475
+ deleteMessage?: (chatId: number, messageId: number) => Promise<void>;
474
476
  }
475
477
 
476
478
  export function createTelegramMenuCallbackHandler<
@@ -514,6 +516,7 @@ export function createTelegramMenuCallbackHandlerForContext<
514
516
  editInteractiveMessage: deps.editInteractiveMessage,
515
517
  sendInteractiveMessage: deps.sendInteractiveMessage,
516
518
  enqueueSectionPrompt: deps.enqueueSectionPrompt,
519
+ deleteMessage: deps.deleteMessage,
517
520
  });
518
521
  }
519
522
 
@@ -570,6 +573,7 @@ export async function handleTelegramMenuCallbackRuntime<
570
573
  enqueuePrompt: deps.enqueueSectionPrompt
571
574
  ? (prompt: string) => deps.enqueueSectionPrompt!(prompt, ctx)
572
575
  : async () => {},
576
+ deleteMessage: deps.deleteMessage ?? (async () => {}),
573
577
  },
574
578
  );
575
579
  if (handled) return;
@@ -590,6 +594,7 @@ export async function handleTelegramMenuCallbackRuntime<
590
594
  enqueuePrompt: deps.enqueueSectionPrompt
591
595
  ? (prompt: string) => deps.enqueueSectionPrompt!(prompt, ctx)
592
596
  : async () => {},
597
+ deleteMessage: deps.deleteMessage ?? (async () => {}),
593
598
  },
594
599
  );
595
600
  if (handled) return;
@@ -612,6 +617,7 @@ export async function handleTelegramMenuCallbackRuntime<
612
617
  enqueuePrompt: deps.enqueueSectionPrompt
613
618
  ? (prompt: string) => deps.enqueueSectionPrompt!(prompt, ctx)
614
619
  : async () => {},
620
+ deleteMessage: deps.deleteMessage ?? (async () => {}),
615
621
  },
616
622
  );
617
623
  if (handled) return;
package/lib/routing.ts CHANGED
@@ -96,6 +96,7 @@ export interface TelegramInboundRouteRuntimeDeps<
96
96
  mode: "html" | "plain",
97
97
  replyMarkup: Menu.TelegramReplyMarkup,
98
98
  ) => Promise<number | undefined>;
99
+ deleteMessage?: (chatId: number, messageId: number) => Promise<void>;
99
100
  answerGuestQuery: (guestQueryId: string, text?: string) => Promise<void>;
100
101
  sendTextReply: (
101
102
  chatId: number,
@@ -193,6 +194,7 @@ export function createTelegramInboundRouteRuntime<
193
194
  sectionRegistry: deps.sectionRegistry,
194
195
  editInteractiveMessage: deps.editInteractiveMessage,
195
196
  sendInteractiveMessage: deps.sendInteractiveMessage,
197
+ deleteMessage: deps.deleteMessage,
196
198
  enqueueSectionPrompt: async (prompt: string, ctx: TContext) => {
197
199
  const chatId = deps.configStore.getAllowedUserId();
198
200
  if (typeof chatId !== "number") return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"