@llblab/pi-telegram 0.9.6 → 0.9.7
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/BACKLOG.md +2 -6
- package/CHANGELOG.md +12 -0
- package/README.md +3 -16
- package/docs/extension-sections.md +1 -1
- package/index.ts +3 -0
- package/lib/api.ts +58 -17
- package/lib/pi.ts +1 -1
- package/lib/polling.ts +1 -0
- package/lib/preview.ts +12 -2
- package/lib/queue.ts +25 -0
- package/lib/routing.ts +31 -0
- package/lib/runtime.ts +2 -1
- package/lib/updates.ts +97 -3
- package/package.json +10 -7
package/BACKLOG.md
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Open Work
|
|
4
4
|
|
|
5
|
-
- Implement Telegram Extension Sections Platform for the 0.10.0 line.
|
|
5
|
+
- [ ] Implement Telegram Extension Sections Platform for the 0.10.0 line.
|
|
6
6
|
- Exit: Runtime registry, main-menu integration, `section:` callback routing, safe section context ports, diagnostics, docs, and at least one small demo/fixture prove ordinary pi extensions can add Telegram menu sections without owning a second poller.
|
|
7
|
-
- Explore always-available outbound Telegram tools for queued artifacts and controls.
|
|
7
|
+
- [ ] Explore always-available outbound Telegram tools for queued artifacts and controls.
|
|
8
8
|
- Priority: Low.
|
|
9
9
|
- Idea: Provide tools such as `telegram_attach_file` and `telegram_attach_button` that can be called outside an active Telegram turn, using the paired chat/session as the delivery target when safe.
|
|
10
10
|
- Exit: Design note defines active-turn versus ambient delivery semantics, safety constraints, failure modes, and whether the current `telegram_attach` contract should stay turn-scoped or gain an ambient companion.
|
|
11
|
-
- Tighten dependency posture for reproducible extension development.
|
|
12
|
-
- Priority: Medium.
|
|
13
|
-
- Idea: Replace broad peer dependency `*` ranges and dev dependency `latest` ranges with explicit compatible ranges once the supported pi/Node/TypeScript matrix is clear.
|
|
14
|
-
- Exit: `package.json` documents the supported Node expectation and compatible pi package ranges without over-constraining early-stage extension iteration.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.7: Bot API 10.0 Alignment
|
|
4
|
+
|
|
5
|
+
- `[Dependencies]` Migrated peer dependencies and imports from `@mariozechner/*` to `@earendil-works/*` (`pi-agent-core`, `pi-ai`, `pi-coding-agent`). Impact: the extension now tracks the new `@earendil-works` package scope; transitive `@mariozechner` packages remain in the lockfile until their upstreams migrate.
|
|
6
|
+
- `[Package]` Added `engines: { "node": ">=22.0.0" }` to document the supported Node expectation while keeping dev dependencies on `latest` for early-stage iteration. Impact: users know the minimum Node version without constraining the development dependency matrix prematurely.
|
|
7
|
+
- `[Polling]` Added `"guest_message"` to `TELEGRAM_ALLOWED_UPDATES` so the bot receives guest-mode updates. Impact: without this, guest mentions are silently ignored by Telegram.
|
|
8
|
+
- `[Telegram API]` Updated `sendMessageDraft` wrapper for Bot API 10.0 semantics: removed the empty-text guard, made `text` optional, and added optional `parse_mode`, `entities`, and `message_thread_id` parameters. Impact: preview can now show a "Thinking…" placeholder with empty text, and callers can pass rich formatting through `parse_mode` or `entities`.
|
|
9
|
+
- `[Telegram API]` Added `answerGuestQuery` to the API runtime for Bot API 10.0 Guest Mode support. Impact: callers can reply to guest queries in chats where the bot is not a member. Uses `InlineQueryResultArticle` as the result payload per Bot API 10.0 contract.
|
|
10
|
+
- `[Updates]` Extended inbound update routing to recognize `guest_message` updates. Added `getAuthorizedTelegramGuestMessage`, guest flow action, execution plan, runtime handler, and prompt enqueue support. Unauthorized guest queries receive an "Access denied." reply via `answerGuestQuery`. Guest turns customize the agent-end delivery to use `answerGuestQuery` instead of normal reply transport. Impact: the bridge can now receive and route guest-mode mentions in group chats while preserving the existing private-message authorization model.
|
|
11
|
+
- `[Runtime]` Added typing-loop skip for guest turns (`chatId === 0`) to avoid spurious `sendChatAction` errors in the status bar.
|
|
12
|
+
- `[Tests]` Added regression tests for empty-text draft delivery, undefined-text draft delivery, rich preview with `parse_mode` and `entities`, guest query answers, guest extraction, guest flow classification, guest execution plan, guest deny reply, and guest message routing through the runtime.
|
|
13
|
+
- `[Preview]` Updated `sendDraft` interface in `lib/preview.ts` to accept optional text and formatting options, keeping the preview pipeline aligned with the new API wrapper.
|
|
14
|
+
|
|
3
15
|
## 0.9.6: Runtime Adapter Positioning
|
|
4
16
|
|
|
5
17
|
- `[Package]` Bumped package metadata to `0.9.6` and repositioned the package description from "Better Telegram DM bridge extension for π" to "Telegram Runtime Adapter for π". Impact: package metadata now reflects the runtime adapter/operator-console role rather than a narrow pipe metaphor.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
`pi-telegram` turns a private Telegram DM into a session-local operator console for π. It admits work, preserves context, streams readable replies, keeps busy sessions usable through queues, lets other extensions share one bot, and turns assistant-authored intent into native Telegram artifacts.
|
|
8
8
|
|
|
9
|
-
This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`
|
|
9
|
+
This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -61,24 +61,11 @@ The first user to message the bot becomes the exclusive owner of the adapter. Me
|
|
|
61
61
|
Most day-to-day controls live in the Telegram menu or π commands. A few important runtime knobs intentionally stay in environment variables because they affect bootstrap, networking, or transport limits before a menu can help:
|
|
62
62
|
|
|
63
63
|
- **Bot token bootstrap**: `/telegram-setup` can prefill from `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_KEY`, `TELEGRAM_TOKEN`, or `TELEGRAM_KEY` when no token is already saved.
|
|
64
|
-
- **HTTP/HTTPS proxy**: native `fetch` can use `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` when Node's environment proxy mode is enabled. Use `NODE_USE_ENV_PROXY=1` or start Node with `--use-env-proxy`.
|
|
64
|
+
- **HTTP/HTTPS proxy**: native `fetch` can use `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` when Node's environment proxy mode is enabled. Use `NODE_USE_ENV_PROXY=1` or start Node with `--use-env-proxy`. SOCKS5 is not part of the zero-dependency core. If you need it, run a local HTTP-to-SOCKS bridge or system tunnel and point `HTTP_PROXY` / `HTTPS_PROXY` at the HTTP endpoint.
|
|
65
65
|
- **Agent data root / temp location**: `PI_CODING_AGENT_DIR` changes the base agent directory used for `telegram.json`, locks, generated outbound-handler artifacts, and Telegram temp files. When unset, the adapter uses `~/.pi/agent`, so inbound Telegram files land in `~/.pi/agent/tmp/telegram`.
|
|
66
66
|
- **Inbound file limit**: `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES` changes the default 50 MiB Telegram download limit.
|
|
67
67
|
- **Outbound attachment limit**: `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` changes the default 50 MiB `telegram_attach` delivery limit.
|
|
68
68
|
|
|
69
|
-
Proxy example:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
export HTTPS_PROXY="http://127.0.0.1:8083"
|
|
73
|
-
export HTTP_PROXY="http://127.0.0.1:8083"
|
|
74
|
-
export NO_PROXY="localhost,127.0.0.1"
|
|
75
|
-
export NODE_USE_ENV_PROXY=1
|
|
76
|
-
|
|
77
|
-
pi
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
SOCKS5 is not part of the zero-dependency core. If you need it, run a local HTTP-to-SOCKS bridge or system tunnel and point `HTTP_PROXY` / `HTTPS_PROXY` at the HTTP endpoint.
|
|
81
|
-
|
|
82
69
|
## Use
|
|
83
70
|
|
|
84
71
|
Once paired, chat with your bot in Telegram. Text, images, files, replies, edits, media groups, and configured handler output are forwarded into π as Telegram-originated turns.
|
|
@@ -134,7 +121,7 @@ The inline application menu is the primary operator surface. It exposes status,
|
|
|
134
121
|
|
|
135
122
|
Messages sent while π is busy enter the prompt queue and are processed in order. Control actions and model-switch continuation turns use higher-priority lanes so operational commands can resume before normal prompts.
|
|
136
123
|
|
|
137
|
-
The menu is the primary way to inspect and mutate the queue. Reactions are an extra shortcut when Telegram delivers `message_reaction` updates for the chat: `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting work; `👎`, `👻`, `💔`, `💩`, and `🗑` remove it. The
|
|
124
|
+
The menu is the primary way to inspect and mutate the queue. Reactions are an extra shortcut when Telegram delivers `message_reaction` updates for the chat: `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting work; `👎`, `👻`, `💔`, `💩`, and `🗑` remove it. The same rules apply to text, voice, files, images, and media groups.
|
|
138
125
|
|
|
139
126
|
### Streaming and Telegram HTML rendering
|
|
140
127
|
|
|
@@ -114,7 +114,7 @@ unregister();
|
|
|
114
114
|
Sections are registered by normal pi extensions:
|
|
115
115
|
|
|
116
116
|
```ts
|
|
117
|
-
import type { ExtensionAPI } from "@
|
|
117
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
118
118
|
import { registerTelegramSection } from "@llblab/pi-telegram/lib/extension-sections.ts";
|
|
119
119
|
|
|
120
120
|
export default function (pi: ExtensionAPI) {
|
package/index.ts
CHANGED
|
@@ -148,6 +148,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
148
148
|
downloadFile: downloadTelegramBridgeFile,
|
|
149
149
|
editMessageText: editTelegramMessageText,
|
|
150
150
|
answerCallbackQuery,
|
|
151
|
+
answerGuestQuery,
|
|
151
152
|
prepareTempDir,
|
|
152
153
|
} = Api.createDefaultTelegramBridgeApiRuntime({
|
|
153
154
|
getBotToken: configStore.getBotToken,
|
|
@@ -333,6 +334,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
333
334
|
updateStatus,
|
|
334
335
|
dispatchNextQueuedTelegramTurn,
|
|
335
336
|
answerCallbackQuery,
|
|
337
|
+
answerGuestQuery,
|
|
336
338
|
sendTextReply,
|
|
337
339
|
setMyCommands,
|
|
338
340
|
getCommands,
|
|
@@ -494,6 +496,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
494
496
|
sendMarkdownReply,
|
|
495
497
|
sendTextReply,
|
|
496
498
|
sendQueuedAttachments: queuedAttachmentSender,
|
|
499
|
+
answerGuestQuery,
|
|
497
500
|
planOutboundReply: outboundReplyPlanner,
|
|
498
501
|
sendOutboundReplyArtifacts: outboundReplyArtifactSender,
|
|
499
502
|
isCurrentOwner: lockOwnershipGuard.ownsContext,
|
package/lib/api.ts
CHANGED
|
@@ -151,12 +151,25 @@ export interface TelegramMessageReactionUpdated {
|
|
|
151
151
|
date: number;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
export interface TelegramGuestMessage {
|
|
155
|
+
message_id: number;
|
|
156
|
+
from?: TelegramUser;
|
|
157
|
+
chat: TelegramChat;
|
|
158
|
+
date: number;
|
|
159
|
+
text?: string;
|
|
160
|
+
caption?: string;
|
|
161
|
+
guest_query_id: string;
|
|
162
|
+
guest_bot_caller_user?: TelegramUser;
|
|
163
|
+
guest_bot_caller_chat?: TelegramChat;
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
export interface TelegramUpdate {
|
|
155
167
|
update_id: number;
|
|
156
168
|
message?: TelegramMessage;
|
|
157
169
|
edited_message?: TelegramMessage;
|
|
158
170
|
callback_query?: TelegramCallbackQuery;
|
|
159
171
|
message_reaction?: TelegramMessageReactionUpdated;
|
|
172
|
+
guest_message?: TelegramGuestMessage;
|
|
160
173
|
deleted_business_messages?: { message_ids?: unknown };
|
|
161
174
|
}
|
|
162
175
|
|
|
@@ -184,6 +197,15 @@ export type TelegramEditMessageTextBody = Record<string, unknown> & {
|
|
|
184
197
|
parse_mode?: "HTML";
|
|
185
198
|
};
|
|
186
199
|
|
|
200
|
+
export type TelegramSendMessageDraftBody = Record<string, unknown> & {
|
|
201
|
+
chat_id: number;
|
|
202
|
+
draft_id: number;
|
|
203
|
+
text?: string;
|
|
204
|
+
parse_mode?: string;
|
|
205
|
+
entities?: unknown[];
|
|
206
|
+
message_thread_id?: number;
|
|
207
|
+
};
|
|
208
|
+
|
|
187
209
|
interface TelegramApiResponse<T> {
|
|
188
210
|
ok: boolean;
|
|
189
211
|
result?: T;
|
|
@@ -233,6 +255,7 @@ export interface TelegramApiClient {
|
|
|
233
255
|
callbackQueryId: string,
|
|
234
256
|
text?: string,
|
|
235
257
|
) => Promise<void>;
|
|
258
|
+
answerGuestQuery?: (guestQueryId: string, text?: string) => Promise<void>;
|
|
236
259
|
}
|
|
237
260
|
|
|
238
261
|
export interface TelegramBridgeApiRuntimeDeps {
|
|
@@ -275,7 +298,12 @@ export interface TelegramBridgeApiRuntime {
|
|
|
275
298
|
sendMessageDraft: (
|
|
276
299
|
chatId: number,
|
|
277
300
|
draftId: number,
|
|
278
|
-
text
|
|
301
|
+
text?: string,
|
|
302
|
+
options?: {
|
|
303
|
+
parse_mode?: string;
|
|
304
|
+
entities?: unknown[];
|
|
305
|
+
message_thread_id?: number;
|
|
306
|
+
},
|
|
279
307
|
) => Promise<boolean>;
|
|
280
308
|
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
281
309
|
editMessageText: (
|
|
@@ -285,6 +313,7 @@ export interface TelegramBridgeApiRuntime {
|
|
|
285
313
|
callbackQueryId: string,
|
|
286
314
|
text?: string,
|
|
287
315
|
) => Promise<void>;
|
|
316
|
+
answerGuestQuery: (guestQueryId: string, text?: string) => Promise<void>;
|
|
288
317
|
prepareTempDir: () => Promise<number>;
|
|
289
318
|
}
|
|
290
319
|
|
|
@@ -534,9 +563,7 @@ export async function fetchTelegramBotIdentity(
|
|
|
534
563
|
botToken: string,
|
|
535
564
|
fetchImpl: typeof fetch = fetch,
|
|
536
565
|
): Promise<TelegramBotIdentityResponse> {
|
|
537
|
-
const response = await fetchImpl(
|
|
538
|
-
`${TELEGRAM_API_BASE}/bot${botToken}/getMe`,
|
|
539
|
-
);
|
|
566
|
+
const response = await fetchImpl(`${TELEGRAM_API_BASE}/bot${botToken}/getMe`);
|
|
540
567
|
return response.json() as Promise<TelegramBotIdentityResponse>;
|
|
541
568
|
}
|
|
542
569
|
|
|
@@ -559,14 +586,11 @@ export async function callTelegramMultipart<TResponse>(
|
|
|
559
586
|
form.set(key, value);
|
|
560
587
|
}
|
|
561
588
|
form.set(fileField, fileBlob, fileName);
|
|
562
|
-
return fetch(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
signal: options?.signal,
|
|
568
|
-
},
|
|
569
|
-
);
|
|
589
|
+
return fetch(`${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`, {
|
|
590
|
+
method: "POST",
|
|
591
|
+
body: form,
|
|
592
|
+
signal: options?.signal,
|
|
593
|
+
});
|
|
570
594
|
},
|
|
571
595
|
options,
|
|
572
596
|
);
|
|
@@ -732,13 +756,18 @@ export function createTelegramBridgeApiRuntime(
|
|
|
732
756
|
}),
|
|
733
757
|
"typing",
|
|
734
758
|
),
|
|
735
|
-
sendMessageDraft: (chatId, draftId, text) => {
|
|
736
|
-
|
|
737
|
-
return callRecorded<boolean>("sendMessageDraft", {
|
|
759
|
+
sendMessageDraft: (chatId, draftId, text, options) => {
|
|
760
|
+
const body: Record<string, unknown> = {
|
|
738
761
|
chat_id: chatId,
|
|
739
762
|
draft_id: draftId,
|
|
740
|
-
|
|
741
|
-
|
|
763
|
+
};
|
|
764
|
+
if (text !== undefined) body.text = text;
|
|
765
|
+
if (options?.parse_mode !== undefined)
|
|
766
|
+
body.parse_mode = options.parse_mode;
|
|
767
|
+
if (options?.entities !== undefined) body.entities = options.entities;
|
|
768
|
+
if (options?.message_thread_id !== undefined)
|
|
769
|
+
body.message_thread_id = options.message_thread_id;
|
|
770
|
+
return callRecorded<boolean>("sendMessageDraft", body);
|
|
742
771
|
},
|
|
743
772
|
sendMessage: (body) =>
|
|
744
773
|
callRecorded<TelegramSentMessage>("sendMessage", body),
|
|
@@ -755,6 +784,18 @@ export function createTelegramBridgeApiRuntime(
|
|
|
755
784
|
answerCallbackQuery: (callbackQueryId, text) => {
|
|
756
785
|
return deps.client.answerCallbackQuery(callbackQueryId, text);
|
|
757
786
|
},
|
|
787
|
+
answerGuestQuery: (guestQueryId, text) => {
|
|
788
|
+
const body: Record<string, unknown> = { guest_query_id: guestQueryId };
|
|
789
|
+
if (text !== undefined) {
|
|
790
|
+
body.result = {
|
|
791
|
+
type: "article",
|
|
792
|
+
id: "1",
|
|
793
|
+
title: text.length > 64 ? text.slice(0, 61) + "..." : text,
|
|
794
|
+
input_message_content: { message_text: text },
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
return callRecorded<void>("answerGuestQuery", body);
|
|
798
|
+
},
|
|
758
799
|
prepareTempDir: () =>
|
|
759
800
|
prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
|
|
760
801
|
};
|
package/lib/pi.ts
CHANGED
package/lib/polling.ts
CHANGED
package/lib/preview.ts
CHANGED
|
@@ -61,7 +61,12 @@ export interface TelegramPreviewRuntimeDeps<
|
|
|
61
61
|
sendDraft: (
|
|
62
62
|
chatId: number,
|
|
63
63
|
draftId: number,
|
|
64
|
-
text
|
|
64
|
+
text?: string,
|
|
65
|
+
options?: {
|
|
66
|
+
parse_mode?: string;
|
|
67
|
+
entities?: unknown[];
|
|
68
|
+
message_thread_id?: number;
|
|
69
|
+
},
|
|
65
70
|
) => Promise<unknown>;
|
|
66
71
|
sendMessage: (
|
|
67
72
|
chatId: number,
|
|
@@ -158,7 +163,12 @@ export interface TelegramPreviewControllerDeps<
|
|
|
158
163
|
sendDraft: (
|
|
159
164
|
chatId: number,
|
|
160
165
|
draftId: number,
|
|
161
|
-
text
|
|
166
|
+
text?: string,
|
|
167
|
+
options?: {
|
|
168
|
+
parse_mode?: string;
|
|
169
|
+
entities?: unknown[];
|
|
170
|
+
message_thread_id?: number;
|
|
171
|
+
},
|
|
162
172
|
) => Promise<unknown>;
|
|
163
173
|
sendMessage: (
|
|
164
174
|
chatId: number,
|
package/lib/queue.ts
CHANGED
|
@@ -66,6 +66,7 @@ export interface TelegramQueueItemBase {
|
|
|
66
66
|
kind: TelegramQueueItemKind;
|
|
67
67
|
chatId: number;
|
|
68
68
|
replyToMessageId: number;
|
|
69
|
+
guestQueryId?: string;
|
|
69
70
|
queueOrder: number;
|
|
70
71
|
queueLane: TelegramQueueLane;
|
|
71
72
|
laneOrder: number;
|
|
@@ -113,6 +114,7 @@ export interface TelegramActiveTurnStore<
|
|
|
113
114
|
clear: () => void;
|
|
114
115
|
getChatId: () => number | undefined;
|
|
115
116
|
getReplyToMessageId: () => number | undefined;
|
|
117
|
+
getGuestQueryId: () => string | undefined;
|
|
116
118
|
getSourceMessageIds: () => number[] | undefined;
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -203,6 +205,7 @@ export function createTelegramActiveTurnStore<
|
|
|
203
205
|
},
|
|
204
206
|
getChatId: () => activeTurn?.chatId,
|
|
205
207
|
getReplyToMessageId: () => activeTurn?.replyToMessageId,
|
|
208
|
+
getGuestQueryId: () => activeTurn?.guestQueryId,
|
|
206
209
|
getSourceMessageIds: () => activeTurn?.sourceMessageIds,
|
|
207
210
|
};
|
|
208
211
|
}
|
|
@@ -785,6 +788,7 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
785
788
|
text: string,
|
|
786
789
|
) => Promise<unknown>;
|
|
787
790
|
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
791
|
+
answerGuestQuery?: (guestQueryId: string, text?: string) => Promise<void>;
|
|
788
792
|
planOutboundReply?: (
|
|
789
793
|
markdown: string,
|
|
790
794
|
) => TelegramAgentEndOutboundReplyPlan<TReplyMarkup>;
|
|
@@ -832,6 +836,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
832
836
|
>["sendMarkdownReply"];
|
|
833
837
|
sendTextReply: TelegramAgentEndRuntimeDeps<TTurn>["sendTextReply"];
|
|
834
838
|
sendQueuedAttachments: (turn: TTurn) => Promise<void>;
|
|
839
|
+
answerGuestQuery?: TelegramAgentEndRuntimeDeps<TTurn>["answerGuestQuery"];
|
|
835
840
|
planOutboundReply?: TelegramAgentEndRuntimeDeps<
|
|
836
841
|
TTurn,
|
|
837
842
|
TReplyMarkup
|
|
@@ -952,6 +957,7 @@ export function createTelegramAgentEndHook<
|
|
|
952
957
|
sendMarkdownReply: deps.sendMarkdownReply,
|
|
953
958
|
sendTextReply: deps.sendTextReply,
|
|
954
959
|
sendQueuedAttachments: deps.sendQueuedAttachments,
|
|
960
|
+
answerGuestQuery: deps.answerGuestQuery,
|
|
955
961
|
planOutboundReply: deps.planOutboundReply,
|
|
956
962
|
sendOutboundReplyArtifacts: deps.sendOutboundReplyArtifacts,
|
|
957
963
|
getDefaultChatId: deps.getDefaultChatId,
|
|
@@ -1007,6 +1013,25 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1007
1013
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1008
1014
|
return;
|
|
1009
1015
|
}
|
|
1016
|
+
if (turn.guestQueryId) {
|
|
1017
|
+
if (deps.isCurrentOwner && !deps.isCurrentOwner()) {
|
|
1018
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (assistant.errorMessage) {
|
|
1022
|
+
await deps.answerGuestQuery?.(
|
|
1023
|
+
turn.guestQueryId,
|
|
1024
|
+
"Telegram bridge: π failed while processing the request.",
|
|
1025
|
+
);
|
|
1026
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (finalText) {
|
|
1030
|
+
await deps.answerGuestQuery?.(turn.guestQueryId, finalText);
|
|
1031
|
+
}
|
|
1032
|
+
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1010
1035
|
if (endPlan.shouldClearPreview) {
|
|
1011
1036
|
await deps.clearPreview(turn.chatId);
|
|
1012
1037
|
}
|
package/lib/routing.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { TelegramBridgeRuntime } from "./runtime.ts";
|
|
|
17
17
|
import * as TextGroups from "./text-groups.ts";
|
|
18
18
|
import * as Turns from "./turns.ts";
|
|
19
19
|
import * as Updates from "./updates.ts";
|
|
20
|
+
import type { TelegramUser } from "./updates.ts";
|
|
20
21
|
|
|
21
22
|
export type TelegramRoutedMessage = Updates.TelegramUpdateMessage &
|
|
22
23
|
Media.TelegramMediaMessage &
|
|
@@ -80,6 +81,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
80
81
|
callbackQueryId: string,
|
|
81
82
|
text?: string,
|
|
82
83
|
) => Promise<void>;
|
|
84
|
+
answerGuestQuery: (guestQueryId: string, text?: string) => Promise<void>;
|
|
83
85
|
sendTextReply: (
|
|
84
86
|
chatId: number,
|
|
85
87
|
replyToMessageId: number,
|
|
@@ -364,6 +366,33 @@ export function createTelegramInboundRouteRuntime<
|
|
|
364
366
|
...deps.telegramQueueStore,
|
|
365
367
|
updateStatus: deps.updateStatus,
|
|
366
368
|
});
|
|
369
|
+
const handleAuthorizedTelegramGuestMessage = async (
|
|
370
|
+
guestMessage: Updates.TelegramGuestMessage & { from: TelegramUser },
|
|
371
|
+
ctx: TContext,
|
|
372
|
+
): Promise<void> => {
|
|
373
|
+
const text = guestMessage.text ?? "";
|
|
374
|
+
const order = deps.bridgeRuntime.queue.allocateItemOrder();
|
|
375
|
+
const guestTurn: Queue.PendingTelegramTurn = {
|
|
376
|
+
kind: "prompt",
|
|
377
|
+
chatId: 0,
|
|
378
|
+
replyToMessageId: 0,
|
|
379
|
+
guestQueryId: guestMessage.guest_query_id,
|
|
380
|
+
sourceMessageIds: [],
|
|
381
|
+
queueOrder: order,
|
|
382
|
+
queueLane: "default",
|
|
383
|
+
laneOrder: order,
|
|
384
|
+
queuedAttachments: [],
|
|
385
|
+
content: [{ type: "text", text }],
|
|
386
|
+
historyText: text,
|
|
387
|
+
statusSummary: Turns.truncateTelegramQueueSummary(text),
|
|
388
|
+
};
|
|
389
|
+
const items = deps.telegramQueueStore.getQueuedItems();
|
|
390
|
+
deps.telegramQueueStore.setQueuedItems(
|
|
391
|
+
Queue.appendTelegramQueueItem(items, guestTurn),
|
|
392
|
+
);
|
|
393
|
+
deps.updateStatus(ctx);
|
|
394
|
+
deps.dispatchNextQueuedTelegramTurn(ctx);
|
|
395
|
+
};
|
|
367
396
|
return Updates.createTelegramPairedUpdateRuntime<TContext, TUpdate>({
|
|
368
397
|
getAllowedUserId: deps.configStore.getAllowedUserId,
|
|
369
398
|
setAllowedUserId: deps.configStore.setAllowedUserId,
|
|
@@ -377,9 +406,11 @@ export function createTelegramInboundRouteRuntime<
|
|
|
377
406
|
prioritizeQueuedTelegramTurnByMessageId:
|
|
378
407
|
deps.queueMutationRuntime.prioritizeByMessageId,
|
|
379
408
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
409
|
+
answerGuestQuery: deps.answerGuestQuery,
|
|
380
410
|
handleAuthorizedTelegramCallbackQuery: callbackHandler,
|
|
381
411
|
sendTextReply: deps.sendTextReply,
|
|
382
412
|
handleAuthorizedTelegramMessage: textDispatch.handleMessage,
|
|
383
413
|
handleAuthorizedTelegramEditedMessage: editRuntime.updateFromEditedMessage,
|
|
414
|
+
handleAuthorizedTelegramGuestMessage,
|
|
384
415
|
});
|
|
385
416
|
}
|
package/lib/runtime.ts
CHANGED
|
@@ -365,7 +365,8 @@ export function startTelegramTypingLoop(
|
|
|
365
365
|
state: TelegramBridgeRuntimeState,
|
|
366
366
|
deps: TelegramTypingLoopDeps,
|
|
367
367
|
): boolean {
|
|
368
|
-
if (state.typingInterval || deps.chatId === undefined
|
|
368
|
+
if (state.typingInterval || deps.chatId === undefined || deps.chatId === 0)
|
|
369
|
+
return false;
|
|
369
370
|
const sendTyping = (): void => {
|
|
370
371
|
void deps.sendTypingAction(deps.chatId as number);
|
|
371
372
|
};
|
package/lib/updates.ts
CHANGED
|
@@ -130,10 +130,19 @@ export interface TelegramCallbackQuery {
|
|
|
130
130
|
message?: TelegramUpdateMessage;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
export interface TelegramGuestMessage {
|
|
134
|
+
guest_query_id: string;
|
|
135
|
+
chat: TelegramChat;
|
|
136
|
+
from?: TelegramUser;
|
|
137
|
+
message_id?: number;
|
|
138
|
+
text?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
133
141
|
export interface TelegramUpdateRouting {
|
|
134
142
|
message?: TelegramUpdateMessage;
|
|
135
143
|
edited_message?: TelegramUpdateMessage;
|
|
136
144
|
callback_query?: TelegramCallbackQuery;
|
|
145
|
+
guest_message?: TelegramGuestMessage;
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
export function getAuthorizedTelegramCallbackQuery(
|
|
@@ -178,6 +187,16 @@ export function getAuthorizedTelegramEditedMessage(
|
|
|
178
187
|
return message;
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
export function getAuthorizedTelegramGuestMessage(
|
|
191
|
+
update: TelegramUpdateRouting,
|
|
192
|
+
): TelegramGuestMessage | undefined {
|
|
193
|
+
const guestMessage = update.guest_message;
|
|
194
|
+
if (!guestMessage || !guestMessage.from || guestMessage.from.is_bot) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
return guestMessage;
|
|
198
|
+
}
|
|
199
|
+
|
|
181
200
|
// --- Flow ---
|
|
182
201
|
|
|
183
202
|
export interface TelegramMessageReactionUpdated {
|
|
@@ -198,6 +217,7 @@ export type TelegramUpdateFlowAction<
|
|
|
198
217
|
TelegramMessageReactionUpdated,
|
|
199
218
|
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
200
219
|
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
220
|
+
TGuestMessage extends TelegramGuestMessage = TelegramGuestMessage,
|
|
201
221
|
> =
|
|
202
222
|
| { kind: "ignore" }
|
|
203
223
|
| { kind: "deleted"; messageIds: number[] }
|
|
@@ -216,6 +236,11 @@ export type TelegramUpdateFlowAction<
|
|
|
216
236
|
kind: "edited-message";
|
|
217
237
|
message: TMessage & { from: TelegramUser };
|
|
218
238
|
authorization: TelegramAuthorizationState;
|
|
239
|
+
}
|
|
240
|
+
| {
|
|
241
|
+
kind: "guest";
|
|
242
|
+
guestMessage: TGuestMessage & { from: TelegramUser };
|
|
243
|
+
authorization: TelegramAuthorizationState;
|
|
219
244
|
};
|
|
220
245
|
|
|
221
246
|
export function buildTelegramUpdateFlowAction<
|
|
@@ -226,7 +251,8 @@ export function buildTelegramUpdateFlowAction<
|
|
|
226
251
|
): TelegramUpdateFlowAction<
|
|
227
252
|
NonNullable<TUpdate["message_reaction"]>,
|
|
228
253
|
NonNullable<TUpdate["callback_query"]>,
|
|
229
|
-
NonNullable<TUpdate["message"] | TUpdate["edited_message"]
|
|
254
|
+
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>,
|
|
255
|
+
NonNullable<TUpdate["guest_message"]>
|
|
230
256
|
> {
|
|
231
257
|
const deletedMessageIds = extractDeletedTelegramMessageIds(update);
|
|
232
258
|
if (deletedMessageIds.length > 0) {
|
|
@@ -272,6 +298,19 @@ export function buildTelegramUpdateFlowAction<
|
|
|
272
298
|
),
|
|
273
299
|
};
|
|
274
300
|
}
|
|
301
|
+
const guestMessage = getAuthorizedTelegramGuestMessage(update);
|
|
302
|
+
if (guestMessage?.from) {
|
|
303
|
+
return {
|
|
304
|
+
kind: "guest",
|
|
305
|
+
guestMessage: guestMessage as NonNullable<TUpdate["guest_message"]> & {
|
|
306
|
+
from: TelegramUser;
|
|
307
|
+
},
|
|
308
|
+
authorization: getTelegramAuthorizationState(
|
|
309
|
+
guestMessage.from.id,
|
|
310
|
+
allowedUserId,
|
|
311
|
+
),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
275
314
|
return { kind: "ignore" };
|
|
276
315
|
}
|
|
277
316
|
|
|
@@ -282,6 +321,7 @@ export type TelegramUpdateExecutionPlan<
|
|
|
282
321
|
TelegramMessageReactionUpdated,
|
|
283
322
|
TCallbackQuery extends TelegramCallbackQuery = TelegramCallbackQuery,
|
|
284
323
|
TMessage extends TelegramUpdateMessage = TelegramUpdateMessage,
|
|
324
|
+
TGuestMessage extends TelegramGuestMessage = TelegramGuestMessage,
|
|
285
325
|
> =
|
|
286
326
|
| { kind: "ignore" }
|
|
287
327
|
| { kind: "deleted"; messageIds: number[] }
|
|
@@ -307,15 +347,31 @@ export type TelegramUpdateExecutionPlan<
|
|
|
307
347
|
message: TMessage & { from: TelegramUser };
|
|
308
348
|
shouldPair: boolean;
|
|
309
349
|
shouldDeny: boolean;
|
|
350
|
+
}
|
|
351
|
+
| {
|
|
352
|
+
kind: "guest";
|
|
353
|
+
guestMessage: TGuestMessage & { from: TelegramUser };
|
|
354
|
+
shouldDeny: boolean;
|
|
310
355
|
};
|
|
311
356
|
|
|
312
357
|
export function buildTelegramUpdateExecutionPlan<
|
|
313
358
|
TReactionUpdate extends TelegramMessageReactionUpdated,
|
|
314
359
|
TCallbackQuery extends TelegramCallbackQuery,
|
|
315
360
|
TMessage extends TelegramUpdateMessage,
|
|
361
|
+
TGuestMessage extends TelegramGuestMessage,
|
|
316
362
|
>(
|
|
317
|
-
action: TelegramUpdateFlowAction<
|
|
318
|
-
|
|
363
|
+
action: TelegramUpdateFlowAction<
|
|
364
|
+
TReactionUpdate,
|
|
365
|
+
TCallbackQuery,
|
|
366
|
+
TMessage,
|
|
367
|
+
TGuestMessage
|
|
368
|
+
>,
|
|
369
|
+
): TelegramUpdateExecutionPlan<
|
|
370
|
+
TReactionUpdate,
|
|
371
|
+
TCallbackQuery,
|
|
372
|
+
TMessage,
|
|
373
|
+
TGuestMessage
|
|
374
|
+
> {
|
|
319
375
|
switch (action.kind) {
|
|
320
376
|
case "ignore":
|
|
321
377
|
return { kind: "ignore" };
|
|
@@ -345,6 +401,12 @@ export function buildTelegramUpdateExecutionPlan<
|
|
|
345
401
|
shouldPair: action.authorization.kind === "pair",
|
|
346
402
|
shouldDeny: action.authorization.kind === "deny",
|
|
347
403
|
};
|
|
404
|
+
case "guest":
|
|
405
|
+
return {
|
|
406
|
+
kind: "guest",
|
|
407
|
+
guestMessage: action.guestMessage,
|
|
408
|
+
shouldDeny: action.authorization.kind === "deny",
|
|
409
|
+
};
|
|
348
410
|
}
|
|
349
411
|
}
|
|
350
412
|
|
|
@@ -387,6 +449,7 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
387
449
|
callbackQueryId: string,
|
|
388
450
|
text?: string,
|
|
389
451
|
) => Promise<void>;
|
|
452
|
+
answerGuestQuery: (guestQueryId: string, text?: string) => Promise<void>;
|
|
390
453
|
handleAuthorizedTelegramCallbackQuery: (
|
|
391
454
|
query: TCallbackQuery,
|
|
392
455
|
ctx: TContext,
|
|
@@ -404,6 +467,10 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
404
467
|
message: TMessage,
|
|
405
468
|
ctx: TContext,
|
|
406
469
|
) => unknown;
|
|
470
|
+
handleAuthorizedTelegramGuestMessage?: (
|
|
471
|
+
guestMessage: TelegramGuestMessage & { from: TelegramUser },
|
|
472
|
+
ctx: TContext,
|
|
473
|
+
) => Promise<void>;
|
|
407
474
|
}
|
|
408
475
|
|
|
409
476
|
export interface TelegramUpdateRuntimeControllerDeps<
|
|
@@ -431,6 +498,7 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
431
498
|
callbackQueryId: string,
|
|
432
499
|
text?: string,
|
|
433
500
|
) => Promise<void>;
|
|
501
|
+
answerGuestQuery: (guestQueryId: string, text?: string) => Promise<void>;
|
|
434
502
|
handleAuthorizedTelegramCallbackQuery: (
|
|
435
503
|
query: TCallbackQuery,
|
|
436
504
|
ctx: TContext,
|
|
@@ -448,6 +516,10 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
448
516
|
message: TMessage,
|
|
449
517
|
ctx: TContext,
|
|
450
518
|
) => unknown;
|
|
519
|
+
handleAuthorizedTelegramGuestMessage?: (
|
|
520
|
+
guestMessage: TelegramGuestMessage & { from: TelegramUser },
|
|
521
|
+
ctx: TContext,
|
|
522
|
+
) => Promise<void>;
|
|
451
523
|
}
|
|
452
524
|
|
|
453
525
|
export interface TelegramUpdateRuntimeController<
|
|
@@ -536,12 +608,15 @@ export function createTelegramPairedUpdateRuntime<
|
|
|
536
608
|
updateStatus: deps.updateStatus,
|
|
537
609
|
}).pairIfNeeded,
|
|
538
610
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
611
|
+
answerGuestQuery: deps.answerGuestQuery,
|
|
539
612
|
handleAuthorizedTelegramCallbackQuery:
|
|
540
613
|
deps.handleAuthorizedTelegramCallbackQuery,
|
|
541
614
|
sendTextReply: deps.sendTextReply,
|
|
542
615
|
handleAuthorizedTelegramMessage: deps.handleAuthorizedTelegramMessage,
|
|
543
616
|
handleAuthorizedTelegramEditedMessage:
|
|
544
617
|
deps.handleAuthorizedTelegramEditedMessage,
|
|
618
|
+
handleAuthorizedTelegramGuestMessage:
|
|
619
|
+
deps.handleAuthorizedTelegramGuestMessage,
|
|
545
620
|
});
|
|
546
621
|
}
|
|
547
622
|
|
|
@@ -582,12 +657,15 @@ export function createTelegramUpdateRuntime<
|
|
|
582
657
|
handleAuthorizedTelegramReactionUpdate: handleAuthorizedReactionUpdate,
|
|
583
658
|
pairTelegramUserIfNeeded: deps.pairTelegramUserIfNeeded,
|
|
584
659
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
660
|
+
answerGuestQuery: deps.answerGuestQuery,
|
|
585
661
|
handleAuthorizedTelegramCallbackQuery:
|
|
586
662
|
deps.handleAuthorizedTelegramCallbackQuery,
|
|
587
663
|
sendTextReply: deps.sendTextReply,
|
|
588
664
|
handleAuthorizedTelegramMessage: deps.handleAuthorizedTelegramMessage,
|
|
589
665
|
handleAuthorizedTelegramEditedMessage:
|
|
590
666
|
deps.handleAuthorizedTelegramEditedMessage,
|
|
667
|
+
handleAuthorizedTelegramGuestMessage:
|
|
668
|
+
deps.handleAuthorizedTelegramGuestMessage,
|
|
591
669
|
}),
|
|
592
670
|
};
|
|
593
671
|
}
|
|
@@ -712,6 +790,22 @@ export async function executeTelegramUpdatePlan<
|
|
|
712
790
|
await deps.handleAuthorizedTelegramCallbackQuery(plan.query, deps.ctx);
|
|
713
791
|
return;
|
|
714
792
|
}
|
|
793
|
+
if (plan.kind === "guest") {
|
|
794
|
+
if (plan.shouldDeny) {
|
|
795
|
+
await deps.answerGuestQuery(
|
|
796
|
+
plan.guestMessage.guest_query_id,
|
|
797
|
+
"Access denied.",
|
|
798
|
+
);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (deps.handleAuthorizedTelegramGuestMessage) {
|
|
802
|
+
await deps.handleAuthorizedTelegramGuestMessage(
|
|
803
|
+
plan.guestMessage,
|
|
804
|
+
deps.ctx,
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
715
809
|
const pairedNow = plan.shouldPair
|
|
716
810
|
? await deps.pairTelegramUserIfNeeded(plan.message.from.id, deps.ctx)
|
|
717
811
|
: false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
5
8
|
"description": "Telegram Runtime Adapter for π",
|
|
6
9
|
"type": "module",
|
|
7
10
|
"keywords": [
|
|
@@ -20,6 +23,9 @@
|
|
|
20
23
|
"bugs": {
|
|
21
24
|
"url": "https://github.com/llblab/pi-telegram/issues"
|
|
22
25
|
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.0.0"
|
|
28
|
+
},
|
|
23
29
|
"scripts": {
|
|
24
30
|
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
25
31
|
"typecheck": "tsc --noEmit",
|
|
@@ -37,9 +43,6 @@
|
|
|
37
43
|
"docs/",
|
|
38
44
|
"screenshot.png"
|
|
39
45
|
],
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"access": "public"
|
|
42
|
-
},
|
|
43
46
|
"pi": {
|
|
44
47
|
"extensions": [
|
|
45
48
|
"./index.ts"
|
|
@@ -47,9 +50,9 @@
|
|
|
47
50
|
"image": "https://github.com/llblab/pi-telegram/raw/main/screenshot.png"
|
|
48
51
|
},
|
|
49
52
|
"peerDependencies": {
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
+
"@earendil-works/pi-agent-core": "*",
|
|
54
|
+
"@earendil-works/pi-ai": "*",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
53
56
|
"@sinclair/typebox": "*"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|