@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 +12 -0
- package/README.md +1 -1
- package/docs/extension-sections.md +44 -0
- package/index.ts +2 -0
- package/lib/api.ts +21 -0
- package/lib/extension-sections.ts +16 -2
- package/lib/menu.ts +6 -0
- package/lib/routing.ts +2 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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;
|