@llblab/pi-telegram 0.27.1 → 0.27.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/AGENTS.md +1 -1
- package/CHANGELOG.md +5 -0
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +1 -1
- package/lib/bindings.ts +1 -0
- package/lib/outbound-attachments.ts +22 -0
- package/lib/prompts.ts +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -115,7 +115,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
115
115
|
- Use `.agents/skills/telegram-bot/SKILL.md` and its `api.md` reference for native Rich Markdown, Bot API topic transport, and transport capability checks
|
|
116
116
|
- Formula guidance belongs in the Telegram-turn prompt contract: use `$...$` for inline math and `$$...$$` for block math; backticks intentionally render formulas as literal code
|
|
117
117
|
- Real code blocks must stay literal and escaped
|
|
118
|
-
- `telegram_attach` is the canonical outbound file-delivery path for Telegram-originated requests; outside active Telegram turns it may send immediately to the paired/default chat, a registered follower's assigned target, or explicit `chat_id` plus optional `thread_id` for explicit local/TUI delivery requests only when this Pi instance owns `/telegram-connect` or is registered with the Threaded Mode multi-instance bus. `telegram_message` is the first-class direct Telegram Markdown text tool
|
|
118
|
+
- `telegram_attach` is the canonical outbound file-delivery path for Telegram-originated requests; outside active Telegram turns it may send immediately to the paired/default chat, a registered follower's assigned target, or explicit `chat_id` plus optional `thread_id` for explicit local/TUI delivery requests only when this Pi instance owns `/telegram-connect` or is registered with the Threaded Mode multi-instance bus. `telegram_message` is the first-class direct Telegram Markdown text tool and follows the same ownership-or-bus-registration gate; during an active Telegram turn it rejects omitted targeting and the exact active target while allowing an explicit different chat/thread target, so it cannot replace the normal current-turn reply but can satisfy requested cross-target delivery. It reuses top-level `telegram_button` comments for inline buttons; buttons must be attached to a text message, never sent as standalone actions
|
|
119
119
|
- Telegram prompt guidance is layered, authority-aware, and context-frugal: only a current direct owner or registered follower exposes `telegram_attach`, `telegram_message`, `telegram_help`, their active-tool prompt metadata, and the compact bridge suffix. Successful disconnect or transport-authority loss removes those surfaces for subsequent requests while preserving every foreign tool; reconnect/recovery restores only the pi-telegram tool subset that was active before suspension. Telegram-originated turns receive only the minimal per-turn contract, and detailed voice/button/direct-delivery/Threaded Mode/formatting/debug guidance belongs in `telegram_help` instead of every prompt.
|
|
120
120
|
- Telegram inbound prompt topology keeps current user material contiguous: `[outputs]` produced from current attachments follows `[attachments]` immediately, then independent `[reply]` or forwarded-source context follows the complete current-user block. Forwarded Rich Message media belongs under source-attributed `[attachments|from:<identity>]` immediately after its `[forward|from:<identity>]` block and remains available as model image content. Do not place source context between an attachment and its transcription or handler output.
|
|
121
121
|
- Forward-annotation grouping distinguishes two semantic layers: the forward owns its source text/caption/media, while an optional separate owner-authored annotation normally arrives immediately before it. Use a bounded one-second candidate window in both transport orders: ordinary short human text may wait for an adjacent same-sender/chat/thread forward, and a forwarded message—including a media-only forward with no source caption—may wait for one adjacent owner annotation. A matching opposite-kind message flushes immediately; same-kind messages, commands, bots, ordinary non-forward captions, media groups, target/sender mismatches, reversed ids, and distant ids stay separate. Preserve the candidate across session replacement and follower forwarding without weakening exact routing-generation authority.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.2: Target-Aware Direct Message Guard
|
|
4
|
+
|
|
5
|
+
- `Direct Message Guard`: `telegram_message` now rejects implicit delivery and an explicit target equal to the active Telegram turn, leaving the ordinary final-reply path as the sole current-target response. An explicit different chat/thread target remains allowed for user-requested cross-target delivery, while local/TUI direct sends and automatic Proactive Push remain unchanged. Impact: accidental duplicate replies are prevented without breaking legitimate inter-thread messaging.
|
|
6
|
+
- `Boundary Coverage`: Focused regressions distinguish implicit, same-target, and different-thread direct messaging while retaining the existing active-turn attachment queue contract. Impact: future duplicate protection cannot silently collapse all direct delivery into a coarse active-turn ban.
|
|
7
|
+
|
|
3
8
|
## 0.27.1: Humanized Tool Labels Hotfix
|
|
4
9
|
|
|
5
10
|
- `Tool Labels`: Tool activity roots now replace snake-case underscores with spaces and capitalize every resulting word while preserving recognizable uppercase repeated-letter prefixes per word (`telegram_attach` → `Telegram Attach`, `ff_find_items` → `FF Find Items`). Impact: Rich tool disclosures and their HTML fallback present multi-word tool names as user-facing labels instead of code identifiers.
|
package/docs/outbound.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`pi-telegram` maps hidden assistant-authored HTML comments to Telegram-native outbound actions.
|
|
4
4
|
|
|
5
|
-
Normal Telegram-turn replies are intentionally prompt-driven: the agent writes Markdown plus small hidden top-level blocks, and the bridge performs transport after `agent_end`. `telegram_voice` and `telegram_button` are not Pi tools. For local/TUI-initiated work where the user explicitly asks to send something to Telegram, the bridge also exposes direct tools: `telegram_message` for Markdown text and `telegram_attach` for file delivery when no Telegram turn is active. In classic mode, direct local/TUI delivery requires this Pi instance to own `/telegram-connect`; in Threaded Mode, a registered follower may route direct-tool sends through the leader-owned bus transport. If
|
|
5
|
+
Normal Telegram-turn replies are intentionally prompt-driven: the agent writes Markdown plus small hidden top-level blocks, and the bridge performs transport after `agent_end`. `telegram_voice` and `telegram_button` are not Pi tools. For local/TUI-initiated work where the user explicitly asks to send something to Telegram, the bridge also exposes direct tools: `telegram_message` for Markdown text and `telegram_attach` for file delivery when no Telegram turn is active. In classic mode, direct local/TUI delivery requires this Pi instance to own `/telegram-connect`; in Threaded Mode, a registered follower may route direct-tool sends through the leader-owned bus transport. During an active Telegram turn, `telegram_message` rejects an implicit or same-turn target so ordinary final delivery remains the sole current-target response, but an explicit different chat/thread target remains available for requested cross-target delivery. If transport authority is absent, the tools fail instead of bypassing singleton ownership. Explicit thread delivery uses `chat_id` plus `thread_id`; registered followers default to their assigned thread target. Outbound behavior combines assistant prompt markup, text command-template handlers, registered voice synthesis providers, generated artifacts, direct Telegram tools, and reply delivery. Direct `telegram_message` text is planned through the same reply markup path, so embedded top-level `telegram_button` comments become buttons attached to that text message.
|
|
6
6
|
|
|
7
7
|
Text handlers use the portable [Command Template Standard](./command-templates.md). Programmatic outbound handlers use `registerTelegramOutboundHandler(kind, handler)`. Voice replies can use configured command-template handlers or the provider API described in [Voice Integration](./voice.md).
|
|
8
8
|
|
package/docs/public-api.md
CHANGED
|
@@ -62,7 +62,7 @@ This command surface is a mobile companion subset, not a raw terminal-command br
|
|
|
62
62
|
### Tools and assistant-authored actions
|
|
63
63
|
|
|
64
64
|
- `telegram_attach(paths, chat_id?, thread_id?, caption?)` is the stable artifact delivery tool for generated files. During Telegram turns it queues files for the active reply; with `assistant.rendering: "rich"`, exactly one PNG/JPEG, MP4, or MP3 artifact plus non-empty final Markdown can become one reply-anchored Rich Message. HTML mode, multiple/unsupported files, Guest Mode, and voice outputs retain their established paths. Outside Telegram turns the tool sends files directly to the paired/default chat, the registered follower's assigned thread, or an explicit `chat_id` plus optional `thread_id` when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus.
|
|
65
|
-
- `telegram_message(text, chat_id?, thread_id?)` sends a direct Telegram Markdown message
|
|
65
|
+
- `telegram_message(text, chat_id?, thread_id?)` sends a direct Telegram Markdown message when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus. During an active Telegram turn, omitted targeting and an explicit target equal to that turn are rejected so the ordinary final-reply path remains the sole current-target response; an explicit different chat/thread target remains allowed for requested cross-target delivery. Outside active turns, paired/default local/TUI delivery remains unchanged. Top-level `telegram_button` comments inside `text` are parsed with the same planner used for normal replies and attached to that message; buttons are never standalone Telegram messages.
|
|
66
66
|
- `telegram_help()` returns detailed agent-facing guidance for pi-telegram delivery actions, Threaded Mode, formatting, and debugging. The regular prompt only points agents at this tool instead of repeating the full guidance on every turn. `telegram_attach`, `telegram_message`, and `telegram_help` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
67
67
|
- `telegram_voice` hidden comments request Telegram-native voice delivery through either a JSON object or double-quoted attributes. Equivalent `text` or `value` supplies the spoken payload; explicit `text` takes precedence.
|
|
68
68
|
- `telegram_button` hidden comments create inline buttons whose taps enqueue prompts through the same two payload forms. Use `label` plus `prompt`, or `value` when both strings are identical. The colon after either action name is optional and does not select the format. Use top-level column-zero comments outside code, quotes, lists, and indented examples; do not emit standalone button actions.
|
package/lib/bindings.ts
CHANGED
|
@@ -158,6 +158,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
158
158
|
OutboundAttachments.registerTelegramOutboundMessageTool(pi, {
|
|
159
159
|
getDefaultChatId,
|
|
160
160
|
getDefaultTarget,
|
|
161
|
+
getActiveTurn: activeTurnRuntime.get,
|
|
161
162
|
canSendDirect,
|
|
162
163
|
planMessage:
|
|
163
164
|
OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore),
|
|
@@ -81,6 +81,9 @@ export interface TelegramOutboundMessagePlan {
|
|
|
81
81
|
export interface TelegramOutboundMessageToolRegistrationDeps extends TelegramOutboundAttachmentRuntimeEventRecorderPort {
|
|
82
82
|
getDefaultChatId: () => number | undefined;
|
|
83
83
|
getDefaultTarget?: () => TelegramTarget | undefined;
|
|
84
|
+
getActiveTurn?: () =>
|
|
85
|
+
| { chatId: number; target?: TelegramTarget }
|
|
86
|
+
| undefined;
|
|
84
87
|
canSendDirect: () => boolean;
|
|
85
88
|
planMessage: (markdown: string) => TelegramOutboundMessagePlan;
|
|
86
89
|
sendMarkdownMessage: (
|
|
@@ -521,6 +524,7 @@ export function registerTelegramOutboundMessageTool(
|
|
|
521
524
|
threadId: params.thread_id,
|
|
522
525
|
getDefaultChatId: deps.getDefaultChatId,
|
|
523
526
|
getDefaultTarget: deps.getDefaultTarget,
|
|
527
|
+
getActiveTurn: deps.getActiveTurn,
|
|
524
528
|
canSendDirect: deps.canSendDirect,
|
|
525
529
|
planMessage: deps.planMessage,
|
|
526
530
|
sendMarkdownMessage: deps.sendMarkdownMessage,
|
|
@@ -735,6 +739,9 @@ export async function sendTelegramOutboundMessage(options: {
|
|
|
735
739
|
target?: TelegramTarget;
|
|
736
740
|
getDefaultChatId?: () => number | undefined;
|
|
737
741
|
getDefaultTarget?: () => TelegramTarget | undefined;
|
|
742
|
+
getActiveTurn?: () =>
|
|
743
|
+
| { chatId: number; target?: TelegramTarget }
|
|
744
|
+
| undefined;
|
|
738
745
|
canSendDirect: () => boolean;
|
|
739
746
|
planMessage: (markdown: string) => TelegramOutboundMessagePlan;
|
|
740
747
|
sendMarkdownMessage: (
|
|
@@ -754,6 +761,21 @@ export async function sendTelegramOutboundMessage(options: {
|
|
|
754
761
|
getDefaultChatId: options.getDefaultChatId,
|
|
755
762
|
getDefaultTarget: options.getDefaultTarget,
|
|
756
763
|
});
|
|
764
|
+
const activeTurn = options.getActiveTurn?.();
|
|
765
|
+
if (activeTurn) {
|
|
766
|
+
const hasExplicitTarget =
|
|
767
|
+
options.chatId !== undefined || options.target !== undefined;
|
|
768
|
+
const activeTarget = activeTurn.target ?? { chatId: activeTurn.chatId };
|
|
769
|
+
const requestedTarget = target ?? { chatId };
|
|
770
|
+
const targetsMatch =
|
|
771
|
+
activeTarget.chatId === requestedTarget.chatId &&
|
|
772
|
+
activeTarget.threadId === requestedTarget.threadId;
|
|
773
|
+
if (!hasExplicitTarget || targetsMatch) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
"telegram_message cannot send directly to the active Telegram turn target; return the text normally so the bridge can deliver it once",
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
757
779
|
const plan = options.planMessage(options.text);
|
|
758
780
|
const messageId = await options.sendMarkdownMessage(chatId, plan.markdown, {
|
|
759
781
|
replyMarkup: plan.replyMarkup,
|
package/lib/prompts.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const TELEGRAM_MESSAGE_PROMPT_GUIDELINES = [
|
|
|
31
31
|
"Use telegram_message only when the user explicitly asks to send a message to Telegram from the local/TUI side, or names a concrete Telegram delivery target.",
|
|
32
32
|
"For an explicit thread target, provide chat_id plus thread_id; registered multi-instance followers default to their assigned thread target.",
|
|
33
33
|
"Add buttons by embedding the same top-level telegram_button HTML comments used in normal Telegram replies; Telegram does not support standalone buttons.",
|
|
34
|
-
"
|
|
34
|
+
"During an active Telegram turn, omit telegram_message for the current target and answer normally; use it only with an explicit different chat_id/thread_id when the user requests cross-target delivery.",
|
|
35
35
|
] as const;
|
|
36
36
|
|
|
37
37
|
const TELEGRAM_MODEL_CONTEXT_TOOL_NAMES = new Set([
|