@llblab/pi-telegram 0.9.7 → 0.9.8

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.8: Guest Mode Context
4
+
5
+ - `[Guest Mode]` Extended the [telegram] prefix with |from:user (sender) and |guest:GroupName (source chat for group guest messages) so the agent sees who sent the message and where from. Private guest chats omit the guest: suffix.
6
+ - `[Guest Mode]` Added |from:user to the [reply] block so the agent knows the original author of a replied-to message in guest mode.
7
+ - `[Guest Mode]` Formatted guest prompt text identically to regular DMs through buildTelegramTurnPrompt, including [attachments] and [outputs] sections with file downloads and inbound handler processing.
8
+ - `[Prompts]` Added compact agent guidance explaining guest-mode prefix suffixes (|from:, |guest:) and reply-from context.
9
+
3
10
  ## 0.9.7: Bot API 10.0 Alignment
4
11
 
5
12
  - `[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.
package/lib/api.ts CHANGED
@@ -161,6 +161,7 @@ export interface TelegramGuestMessage {
161
161
  guest_query_id: string;
162
162
  guest_bot_caller_user?: TelegramUser;
163
163
  guest_bot_caller_chat?: TelegramChat;
164
+ reply_to_message?: TelegramMessage;
164
165
  }
165
166
 
166
167
  export interface TelegramUpdate {
package/lib/prompts.ts CHANGED
@@ -12,8 +12,8 @@ const SYSTEM_PROMPT_SUFFIX = `
12
12
  Telegram bridge extension is active.
13
13
 
14
14
  Inbound context:
15
- - \`[telegram]\` marks Telegram-originated messages.
16
- - \`[reply]\` is quoted context from the replied-to message, not a new instruction by itself. Use it to resolve references like "this", "it", or "that message"; the actual instruction is before [reply] unless it explicitly asks to act on the quote.
15
+ - \`[telegram]\` marks Telegram-originated messages. Suffixes \`|from:user\` (sender) and \`|guest:group\` (guest mode — message from another chat where the bot is not a member) may be present; the bot sees the message as if forwarded from that user/chat.
16
+ - \`[reply]\` is quoted context from the replied-to message, not a new instruction by itself. Suffix \`|from:user\` identifies the original author in guest-mode replies. Use it to resolve references like "this", "it", or "that message"; the actual instruction is before [reply] unless it explicitly asks to act on the quote.
17
17
  - \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains inbound-handler stdout such as transcriptions or extracted text for those attachments.
18
18
  - Unknown \`[callback] ...\` messages may be intended for another extension; if you see one, say the callback was not handled and the environment may be misconfigured.
19
19
 
package/lib/routing.ts CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import * as OutboundHandlers from "./outbound-handlers.ts";
8
8
  import * as Commands from "./commands.ts";
9
+ import { readFile } from "node:fs/promises";
9
10
  import type { TelegramConfigStore } from "./config.ts";
10
11
  import type { TelegramInboundHandlerRuntime } from "./inbound-handlers.ts";
11
12
  import * as Media from "./media.ts";
@@ -371,7 +372,71 @@ export function createTelegramInboundRouteRuntime<
371
372
  ctx: TContext,
372
373
  ): Promise<void> => {
373
374
  const text = guestMessage.text ?? "";
375
+ const gm = guestMessage as unknown as Record<string, unknown>;
376
+ // Build telegram prefix with guest context
377
+ const fromRaw = gm.from as Record<string, unknown> | undefined;
378
+ const fromName =
379
+ (fromRaw?.username as string) ||
380
+ (fromRaw?.first_name as string) ||
381
+ "";
382
+ const chatRaw = gm.chat as Record<string, unknown>;
383
+ const chatTitle = chatRaw?.title as string | undefined;
384
+ const chatType = chatRaw?.type as string;
385
+ const prefixParts = ["telegram"];
386
+ if (fromName) prefixParts.push(`from:${fromName}`);
387
+ if (chatType !== "private" && chatTitle) {
388
+ prefixParts.push(`guest:${chatTitle}`);
389
+ }
390
+ const telegramPrefix = `[${prefixParts.join("|")}]`;
391
+ // Extract reply context
392
+ const replyMsg = gm.reply_to_message as Record<string, unknown> | undefined;
393
+ const replyText =
394
+ replyMsg
395
+ ? ((replyMsg.text as string) || (replyMsg.caption as string) || "").trim()
396
+ : "";
397
+ const replyFrom =
398
+ replyMsg
399
+ ? (replyMsg.from as Record<string, unknown> | undefined)?.username as string | undefined
400
+ : undefined;
401
+ // Download files, run inbound handlers
402
+ const guestMsg = guestMessage as unknown as Media.TelegramMediaMessage;
403
+ const files = await Media.downloadTelegramMessageFiles([guestMsg], {
404
+ downloadFile: deps.downloadFile,
405
+ });
406
+ const processed = await deps.inboundHandlerRuntime.process(files, text, ctx);
407
+ let rawText = processed.rawText || text;
408
+ // Append reply context after handler processing
409
+ if (replyText) {
410
+ const replyBlock = replyFrom
411
+ ? `[reply|from:${replyFrom}] ${replyText}`
412
+ : `[reply] ${replyText}`;
413
+ rawText = `${rawText}\n\n${replyBlock}`;
414
+ }
415
+ const promptText = Turns.buildTelegramTurnPrompt({
416
+ telegramPrefix,
417
+ rawText,
418
+ files,
419
+ promptFiles: processed.promptFiles,
420
+ handlerOutputs: processed.handlerOutputs,
421
+ });
374
422
  const order = deps.bridgeRuntime.queue.allocateItemOrder();
423
+ const content: Queue.TelegramPromptContent[] = [
424
+ { type: "text", text: promptText },
425
+ ];
426
+ for (const file of processed.promptFiles) {
427
+ if (file.isImage && file.mimeType) {
428
+ try {
429
+ const buffer = await readFile(file.path);
430
+ content.push({
431
+ type: "image",
432
+ data: Buffer.from(buffer).toString("base64"),
433
+ mimeType: file.mimeType,
434
+ });
435
+ } catch {
436
+ // skip unreadable files
437
+ }
438
+ }
439
+ }
375
440
  const guestTurn: Queue.PendingTelegramTurn = {
376
441
  kind: "prompt",
377
442
  chatId: 0,
@@ -382,9 +447,15 @@ export function createTelegramInboundRouteRuntime<
382
447
  queueLane: "default",
383
448
  laneOrder: order,
384
449
  queuedAttachments: [],
385
- content: [{ type: "text", text }],
386
- historyText: text,
387
- statusSummary: Turns.truncateTelegramQueueSummary(text),
450
+ content,
451
+ historyText: Turns.formatTelegramTurnStatusSummary(
452
+ processed.rawText || text,
453
+ processed.promptFiles,
454
+ processed.handlerOutputs,
455
+ ),
456
+ statusSummary: Turns.truncateTelegramQueueSummary(
457
+ processed.rawText || text,
458
+ ),
388
459
  };
389
460
  const items = deps.telegramQueueStore.getQueuedItems();
390
461
  deps.telegramQueueStore.setQueuedItems(
package/lib/updates.ts CHANGED
@@ -136,6 +136,7 @@ export interface TelegramGuestMessage {
136
136
  from?: TelegramUser;
137
137
  message_id?: number;
138
138
  text?: string;
139
+ reply_to_message?: TelegramUpdateMessage;
139
140
  }
140
141
 
141
142
  export interface TelegramUpdateRouting {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"