@llblab/pi-telegram 0.16.5 → 0.17.0

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 CHANGED
@@ -35,8 +35,8 @@ The core product loop is mobile continuation: start or supervise work in the ter
35
35
  - `/docs/public-api.md`: Stable public API map for commands, config, assistant markup, extension APIs, package entrypoints, and compatibility boundaries
36
36
  - `/README.md`: User-facing project entry point. Keep its rhythm as install → connect → use → core features → docs, with vivid examples that explain the runtime adapter/operator-console model without duplicating full docs.
37
37
  - `/AGENTS.md`: Durable engineering and runtime conventions
38
- - `/BACKLOG.md`: Canonical open work
39
- - `/CHANGELOG.md`: Completed delivery history
38
+ - `/BACKLOG.md`: Canonical open work. Keep only open top-level tasks; when all subtasks under a top-level task are complete, remove that task from the backlog and record completed delivery in `CHANGELOG.md` if user-visible. Put detailed decomposition under the single owning top-level task with nested checkboxes and explicit done criteria instead of promoting completed slices into separate top-level backlog items.
39
+ - `/CHANGELOG.md`: Completed delivery history. Prefer multiple domain-scoped bullets in the form `[Domain]`: change + impact instead of accumulating unrelated changes into one long entry.
40
40
 
41
41
  ## 4. Core Entities
42
42
 
@@ -75,7 +75,9 @@ The core product loop is mobile continuation: start or supervise work in the ter
75
75
 
76
76
  ## 5.3 Telegram Delivery Semantics
77
77
 
78
- - Telegram replies render through Telegram HTML, not raw Markdown
78
+ - Assistant and guest replies use Telegram-native Rich Markdown via Rich Message APIs, not Markdown→HTML conversion. Bridge-owned UI surfaces such as commands, menus, status, queue controls, and sections should keep explicit Telegram HTML/plain rendering by default because readability and maintainability are higher there. Companion sections may explicitly choose Markdown, HTML, or plain text per view
79
+ - Use `docs/telegram-bot-api-rich-messages.md` as the local Bot API/Rich Messages reference for native Rich Markdown work
80
+ - Formula guidance belongs in the Telegram-turn prompt contract: use `$...$` for inline math and `$$...$$` for block math; backticks intentionally render formulas as literal code
79
81
  - Real code blocks must stay literal and escaped
80
82
  - `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 for explicit local/TUI delivery requests only when this π instance owns `/telegram-connect`. `telegram_message` is the first-class direct Telegram Markdown text tool for local/TUI prompts and is also gated by `/telegram-connect`; neither direct tool replaces normal active-turn replies. It reuses top-level `telegram_button` comments for inline buttons; buttons must be attached to a text message, never sent as standalone actions
81
83
  - Telegram delivery strips top-level HTML comments from preview/final text; column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks are special outbound comments handled after `agent_end` without requiring agent-side transport tool calls, while comments inside code, quotes, lists, or indented examples stay literal
@@ -94,7 +96,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
94
96
 
95
97
  - Treat queue handling, compaction interaction, and lifecycle-hook state transitions as regression-prone areas; validate them after changing dispatch logic
96
98
  - Route important runtime failures through the recent runtime event recorder so `/telegram-status` remains useful for post-mortem debugging, not just transient status-bar errors
97
- - Treat Markdown rendering as Telegram-specific output work, not generic Markdown rendering
99
+ - Treat remaining Markdown-to-HTML rendering as Telegram UI/compat output work, not generic Markdown rendering or assistant reply delivery
98
100
  - Preserve literal code content in Telegram rendering
99
101
  - Avoid HTML chunk splits that break tags
100
102
  - Prefer width-efficient monospace table and list formatting for narrow clients, with table padding based on grapheme/display width rather than raw UTF-16 length where possible
@@ -123,13 +125,17 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
123
125
 
124
126
  ## 6.4 Entrypoint And Import Boundaries
125
127
 
126
- - Keep preview appearance logic in the rendering domain and preview transport/lifecycle logic in the preview domain so richer streaming strategies can evolve without entangling Telegram delivery state with Markdown formatting rules
128
+ - Keep the preview domain as a thin streaming lifecycle controller only: draft ids, `sendRichMessageDraft`, voice suppression, serialized flushes, diagnostics, and editable fallback-message state. Do not reintroduce assistant preview rendering there; keep `rendering.ts` scoped to bridge-owned UI/compat regular-message rendering rather than assistant or guest Markdown delivery
129
+ - Preview/final delivery ordering is release-critical: finalization must wait for active preview flushes, persisted final delivery should not be followed by a post-final draft-clear call that creates transient draft UI, and regressions should cover in-flight draft flush serialization plus final reply ordering
130
+ - Live Rich Draft observation: `sendMessageDraft(..., undefined)` after a persisted final Rich Message can appear in Telegram clients as a separate animated three-dot draft block before dissolving. Do not use post-final draft-clear for assistant finalization; let the persisted final `sendRichMessage` replace/complete the user-visible lifecycle and reset local preview state only.
131
+ - `RichBlockThinking` / `<tg-thinking>` is draft-only (`sendRichMessageDraft`) and may be useful for a future explicit pre-token preloader, but it is not a persisted final-message primitive and should not be mixed into release-critical finalization behavior without separate UX tests.
132
+ - Keep Telegram prompt guidance compact and operational. Do not add format-specific steering for native Rich Markdown features unless the model needs a real bridge-specific rule such as formula delimiters or hidden outbound action syntax.
127
133
  - Keep direct `node:*` file-operation dependencies out of `index.ts` when an owning domain exists; the entrypoint should compose ports while domains own local filesystem details such as temp-dir preparation, attachment stats, and turn image reads
128
134
  - In `index.ts`, prefer namespace imports for local bridge domains so orchestration reads as domain-scoped calls such as `Queue.*`, `Turns.*`, and `Rendering.*` instead of long flat import lists
129
135
  - Keep the local `index.ts` plus `/lib/*.ts` import graph acyclic; `tests/invariants.test.ts` guards this boundary plus shared-bucket bans, empty interface-extension shell regressions, pi SDK centralization, source-only entrypoint Node-runtime/local-adapter/process/direct-pi access avoidance, runtime-domain isolation, structural leaf-domain import isolation, menu/model boundary drift, Telegram API/config default coupling, structural update/media coupling to Telegram API transport shapes, and attachment coupling to queue/inbound media/Telegram API helpers as domains keep evolving
130
136
  - Do not reintroduce shared bucket domains such as `lib/constants.ts`, `lib/types.ts`, `lib/globals.ts`, or broad global-augmentation files; constants, registry keys, state interfaces, and concrete transport shapes should stay in their owning domains, and `index.ts` should not grow new shared magic constants
131
137
  - Keep remaining `index.ts` code focused on cross-domain adapter wiring that needs live extension state, pi callbacks, Telegram API ports, or status updates; do not extract one-off closures solely to reduce line count
132
- - Domain-specific queue planning, preview transport/controller behavior, rendering, Telegram API transport, menu state, and command behavior should stay in their owning domains instead of moving to `/lib/runtime.ts` solely to shrink `index.ts`
138
+ - Domain-specific queue planning, preview transport/controller behavior, UI/compat rendering, Telegram API transport, menu state, and command behavior should stay in their owning domains instead of moving to `/lib/runtime.ts` solely to shrink `index.ts`
133
139
  - Prefer narrow structural runtime ports in domains that only store or route pi-compatible values; direct pi SDK/model imports should stay centralized in `/lib/pi.ts`, while domains that actively register pi hooks/tools/commands should consume those concrete contracts through the adapter
134
140
 
135
141
  ## 7. Operational Conventions
@@ -150,7 +156,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
150
156
  - For `/telegram-setup`, prefer the locally saved bot token over environment variables on repeat setup runs; env vars are the bootstrap path when no local token exists, and persisted `telegram.json` writes must remain atomic plus private because status/setup/polling paths may read it concurrently
151
157
  - Command help plus prompt-template commands and status/model/thinking/queue controls are driven through `/start`'s Telegram inline application menu and callback queries; the Queue button shows the queued-item count, model-menu scope/pagination controls stay at the top under Main menu, the model pagination indicator opens a compact page picker, and thinking-menu text stays a compact heading because the current level is marked by button state; `/status`, `/model`, `/thinking`, and `/queue` are hidden compatibility shortcuts
152
158
  - Shared inline-keyboard structure belongs to `keyboard`; application-control button labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue` while core queue mechanics stay in `queue`
153
- - Telegram `/settings` options should open nested detail submenus by default: checkbox options show a description plus Back, `on`, and `off`; list options show Back plus selectable values. One-shot actions such as syncing may run directly without a submenu when there is no meaningful choice or description step.
159
+ - Telegram `/settings` options should open nested detail submenus by default: boolean options show a description plus Back, `on`, and `off`; list options show Back plus selectable values. One-shot actions such as syncing may run directly without a submenu when there is no meaningful choice or description step.
154
160
  - Inline UI labels and dialogs follow [`docs/ui-style.md`](./docs/ui-style.md): action buttons use emoji plus capitalized action text, state & navigation buttons show state and lead to a submenu, first-level submenus start with `⬆️ Main menu` while deeper submenus start with `⬆️ Back`, boolean toggles use Capitalized horizontal `On`/`Off` with green active `On`, yellow active `Off`, and black inactive dots, tabs use capitalized labels with purple default-state and yellow elevated-state active dots and black inactive dots, vertical option lists mark only the current value green, and confirmation dialogs use a single bold text-only question with emoji on buttons only.
155
161
  - Inbound text/media may be transformed through configured `inboundHandlers` before queueing; legacy `attachmentHandlers` are deprecated compatibility aliases appended after `inboundHandlers`; active-turn outbound files must flow through `telegram_attach`, while explicit local/TUI Telegram sends use `telegram_attach` for files or `telegram_message` for text/buttons
156
162
  - Long Telegram text split recovery belongs to `text-groups`: keep it conservative, short-debounced, same chat/user/message-id contiguous, and gated by near-limit human text so normal rapid follow-ups and slash commands stay separate
@@ -167,7 +173,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
167
173
  - `Section identity`: use the same identity-key rules as the Extension Locks Standard (`package.json/name` → canonical id); no separate `owner` field
168
174
  - `Token mapping`: Telegram's 64-byte `callback_data` limit forces compact numeric tokens (`section:0:action:payload`). Section authors never hand-roll `section:` strings — use `ctx.callbackData(action, payload?)`
169
175
  - `Navigation hierarchy`: Back buttons are auto-prepended by `ctx.edit()` only. Root views use `⬆️ Main menu` → `menu:back`. Nested views from `handleCallback` use `⬆️ Back` → `section:<token>:open`. Settings views use `⬆️ Back` → `settings:list`. `ctx.open()` sends standalone chat messages without auto-navigation
170
- - `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. No filesystem access, no raw bot clients, no second polling loop
176
+ - `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. Section views default to `parseMode: "html"`; use explicit `"markdown"` when a section naturally owns Markdown content, or `"plain"` for text. No filesystem access, no raw bot clients, no second polling loop
171
177
  - `Settings indicators`: use `settings.getLabel()` for dynamic status rows in the Settings submenu (e.g., `🟢`/`⚫️` based on internal state). Called on every Settings list render
172
178
  - `Handler fallback`: `section.handleCallback` runs first; if it returns `"pass"` and `settings.handleCallback` exists, the settings handler runs with a fresh context carrying `backCallback="settings:list"`
173
179
  - `Stale tokens`: unknown or unregistered tokens answer the callback with a short popup. Section errors are caught and surfaced as popup text — no unhandled exceptions leak to polling
package/BACKLOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Project Backlog
2
2
 
3
3
  - [ ] Track a public Pi session-replacement hook before adding Telegram `/new`.
4
- - Context: `pi-telegram` is an extension/mobile companion, not a PTY supervisor. A soft `/new` that mutates session internals or filters context without TUI/runtime parity breaks the product boundary.
5
- - Requirement: only add Telegram `/new` when Pi exposes a safe public API that invokes the same session-replacement path as terminal `/new`, including lifecycle, active-run handling, and TUI rerender semantics.
6
- - Rejected for this extension: raw TTY injection, ANSI terminal clearing, private TUI container mutation, or running a shadow `pi` subprocess to control the current session.
4
+ - [ ] Wait for a safe public Pi API.
5
+ - Done when: Pi exposes an API that invokes the same session-replacement path as terminal `/new`.
6
+ - Done when: the API covers lifecycle, active-run handling, and TUI rerender semantics.
7
+ - [x] Keep unsafe implementation routes rejected.
8
+ - Done when: raw TTY injection remains rejected.
9
+ - Done when: ANSI terminal clearing remains rejected.
10
+ - Done when: private TUI container mutation remains rejected.
11
+ - Done when: shadow `pi` subprocess control remains rejected.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.17.0: Native Rich Markdown Delivery
6
+
7
+ - `[Rich Markdown]` Assistant and guest replies now use Telegram-native Rich Message APIs directly: final assistant Markdown goes through `sendRichMessage`, streaming drafts go through `sendRichMessageDraft`, editable fallback previews finalize through `editMessageText.rich_message`, and guest replies use `InputRichMessageContent`. Impact: model-authored Markdown reaches Telegram as native Rich Markdown instead of passing through the legacy Markdown-to-HTML assistant path.
8
+ - `[Preview UX]` Streaming preview is now a thin Rich Draft lifecycle controller with serialized flushes, no default debounce, no assistant rendering dependency, and no post-final draft-clear call. Impact: live Telegram clients get smoother draft updates and avoid duplicate final messages, blank finalization gaps, or the transient animated three-dot block observed during draft clearing.
9
+ - `[UI Boundary]` Bridge-owned commands, menus, status messages, queue controls, buttons, and sections remain explicit Telegram HTML/plain UI by default, while companion sections can opt into Markdown/HTML/plain per view. Impact: native Rich Markdown improves model-authored replies without making hand-authored bot UI harder to maintain.
10
+ - `[API And Limits]` Added typed Rich Message send/draft helpers, disabled automatic entity detection for assistant/guest Rich Markdown, and split native Markdown at Telegram Rich Message character/block limits while keeping reply metadata on the first chunk and reply markup on the final chunk. Impact: technical output avoids accidental entities and long replies stay within Bot API limits.
11
+ - `[Docs And Tests]` Added `docs/telegram-bot-api-rich-messages.md` as the durable local Bot API reference, updated README/outbound/public API/sections/architecture/prompt/AGENTS guidance, kept formula prompting compact around `$...$` / `$$...$$`, and added regressions for native delivery, guest replies, preview lifecycle, split replies, and the UI/compat rendering boundary. Impact: the release behavior is documented, covered, and easier to preserve.
12
+
13
+ ## 0.16.6: Telegram Review Hardening Hotfix
14
+
15
+ - `[Guest Mode]` Deny `guest_message` updates until the bridge already has a paired Telegram user. Impact: guest mode can no longer become the first pairing surface or trigger guest file/handler processing before explicit DM pairing.
16
+ - `[Lifecycle]` Unref the compaction observer fallback timer when the host timer supports it. Impact: headless or shutdown paths are less likely to linger until the 5-minute safety timeout.
17
+ - `[Shutdown]` Stop polling before clearing active-turn/abort state, keep the abort controller visible until the polling promise settles, and record typing-cleanup failures without skipping polling abort. Impact: session shutdown and polling cleanup ordering is more deterministic.
18
+ - `[Replies]` Scope transport-level reply deduplication by chat id. Impact: equal Telegram message ids in different chats no longer suppress valid reply metadata for each other.
19
+ - `[Buttons]` Consume one-shot `telegram_button` callback actions after the first successful resolve. Impact: repeated taps on an old assistant-authored button no longer enqueue duplicate prompts.
20
+ - `[Buttons]` Centralized Telegram `callback_data` byte-limit guards for generated inline keyboards outside the section helper path. Impact: oversized generated button callbacks fail locally before Telegram rejects the message.
21
+ - `[Diagnostics]` Record `answerCallbackQuery` transport failures in runtime API diagnostics while keeping callback handling non-fatal. Impact: `/telegram-status` can explain failed Telegram callback acknowledgements instead of losing the signal silently.
22
+ - `[Tests]` Added regressions for shutdown during pending control, long-text, and media-group dispatch, for settings menu callbacks persisting voice/time changes to `telegram.json`, for malformed/boundary Markdown rendering, and for runtime outbound delivery retrying a transient Telegram API failure. Impact: high-risk queue/timer/settings/rendering/API paths are pinned at the bridge boundary.
23
+ - `[Docs]` Documented which environment-driven transport defaults should be set before launch because module-load constants intentionally capture them.
24
+ - `[Backlog]` Captured and narrowed the non-blocking 2026-06 review-swarm follow-ups for lifecycle shutdown hardening, reply/callback/button state cleanup, validation coverage, and bindings maintainability.
25
+
5
26
  ## 0.16.5: Context-Aware Prompt Guidance Hotfix
6
27
 
7
28
  - `[Prompt Guidance]` Made before-agent-start Telegram guidance context-aware: unconfigured sessions receive no bridge suffix, local/TUI prompts receive only explicit direct-delivery guidance, and Telegram-originated turns keep the full inbound, phone-width, voice, and button contract. Impact: ordinary local replies no longer get raw Telegram action-comment syntax unless the current turn actually comes from Telegram.
package/README.md CHANGED
@@ -68,6 +68,10 @@ Most day-to-day controls live in the Telegram menu or π commands. A few importa
68
68
  - **Inbound file limit**: `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES` changes the default 50 MiB Telegram download limit.
69
69
  - **Outbound attachment limit**: `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` changes the default 50 MiB `telegram_attach` delivery limit.
70
70
 
71
+ Assistant Markdown is delivered through Telegram's native Rich Message API. There is no `telegram.json` rendering toggle: final replies use `sendRichMessage`, and streaming previews use `sendRichMessageDraft` when Telegram drafts are available.
72
+
73
+ Set these variables before launching π. Some transport defaults (notably Telegram temp directory and inbound/outbound byte-limit constants) are intentionally captured when the extension modules load, while setup-token defaults and agent-dir lookups used by config/locks are read through their runtime helpers.
74
+
71
75
  ## Use
72
76
 
73
77
  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.
@@ -128,11 +132,11 @@ The menu is the primary way to inspect and mutate the queue. Reactions are an ex
128
132
  - Priority shortcuts: `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting work.
129
133
  - Removal shortcuts: `👎`, `👻`, `💔`, `💩`, and `🗑` remove waiting work from the queue.
130
134
 
131
- ### Streaming and Telegram HTML rendering
135
+ ### Streaming and native Rich Markdown
132
136
 
133
- Closed Markdown blocks stream back as rich Telegram HTML while π is generating. The growing tail stays conservative until the final rendered reply lands. Long replies are split below Telegram limits without intentionally breaking HTML structures, links, code blocks, blockquotes, lists, or code fences.
137
+ Assistant Markdown is sent to Telegram as native Rich Markdown. Streaming previews use Telegram rich-message drafts when available, and final replies persist the same Markdown through `sendRichMessage`. The bridge still strips top-level hidden action comments before delivery and splits only when Telegram transport limits require it.
134
138
 
135
- Rendering is phone-aware: tables and lists stay narrow, table padding accounts for emoji graphemes and wide Unicode display width, unsupported link forms degrade safely, and block spacing stays faithful to the original Markdown.
139
+ Telegram HTML rendering remains the default for bridge-owned UI surfaces such as commands, menus, status messages, queue controls, and extension sections, where explicit markup is clearer and easier to maintain. Native Rich Markdown is reserved for model-authored Markdown replies and guest replies that naturally arrive as Markdown.
136
140
 
137
141
  ### Media, replies, edits, and split text
138
142
 
@@ -187,7 +191,7 @@ List the main risks first.
187
191
  -->
188
192
  ```
189
193
 
190
- Outbound `type: "text"` handlers can transform final text/Markdown before Telegram rendering and delivery. Voice output can be handled either by configured `outboundHandlers` with `type: "voice"` or by registered voice synthesis provider extensions: the bridge extracts `telegram_voice` text or intercepts text by reply mode, asks the voice pipeline for a `.ogg`/`.opus` artifact, and uploads it through Telegram `sendVoice`. Explicit configured voice handlers run before zero-config providers, so operator-owned `telegram.json` pipelines stay authoritative.
194
+ Outbound `type: "text"` handlers can transform final text/Markdown before native Rich Markdown delivery. Voice output can be handled either by configured `outboundHandlers` with `type: "voice"` or by registered voice synthesis provider extensions: the bridge extracts `telegram_voice` text or intercepts text by reply mode, asks the voice pipeline for a `.ogg`/`.opus` artifact, and uploads it through Telegram `sendVoice`. Explicit configured voice handlers run before zero-config providers, so operator-owned `telegram.json` pipelines stay authoritative.
191
195
 
192
196
  The agent writes intent; providers or voice handlers own TTS and format conversion, the adapter owns Telegram transport, and buttons route back as queued prompts.
193
197
 
@@ -214,7 +218,7 @@ Unknown inline-button callbacks are forwarded to π as `[callback] <data>` when
214
218
 
215
219
  ### Extension Sections
216
220
 
217
- Ordinary pi extensions can register Telegram-native slash commands, structured UI sections, and compact status lines without owning a second polling loop. Slash commands use explicit opt-in registration from `@llblab/pi-telegram/commands`, so workflow-specific commands can live in companion extensions instead of expanding the core bridge command set. UI sections appear in the main Telegram menu and Settings submenu, while status lines allow widgets such as quota indicators to appear beside Status, Usage, Cost, and Context only when relevant to the active model. 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.
221
+ Ordinary pi extensions can register Telegram-native slash commands, structured UI sections, and compact status lines without owning a second polling loop. Slash commands use explicit opt-in registration from `@llblab/pi-telegram/commands`, so workflow-specific commands can live in companion extensions instead of expanding the core bridge command set. UI sections appear in the main Telegram menu and Settings submenu, default to explicit Telegram HTML UI markup, and may explicitly choose Markdown or plain text when that better matches their content; status lines allow widgets such as quota indicators to appear beside Status, Usage, Cost, and Context only when relevant to the active model. 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.
218
222
 
219
223
  Import `registerTelegramSection()` from `@llblab/pi-telegram/sections` 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/sections.md) for the full contract.
220
224
 
package/docs/README.md CHANGED
@@ -6,8 +6,9 @@ Living index of project documentation in `/docs`.
6
6
 
7
7
  ## Documents
8
8
 
9
- - [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, queueing model, rendering pipeline, and interactive controls
9
+ - [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, queueing model, native Rich Markdown delivery, UI/compat rendering, and interactive controls
10
10
  - [public-api.md](./public-api.md) — Stable public API map: package entrypoints, commands, config, assistant markup, extension APIs, smoke examples, and compatibility boundaries
11
+ - [telegram-bot-api-rich-messages.md](./telegram-bot-api-rich-messages.md) — Local Bot API Rich Messages reference used for native Rich Markdown delivery work
11
12
  - [command-templates.md](./command-templates.md) — Portable command-template standard core
12
13
  - [inbound.md](./inbound.md) — Local `pi-telegram` inbound text/media handler bus, programmatic inbound handlers, registered STT provider fallbacks, legacy `attachmentHandlers` compatibility, placeholders, and fallbacks
13
14
  - [outbound.md](./outbound.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, voice synthesis provider fallback priority, artifact outputs, and callback routing
@@ -58,7 +58,7 @@ The repository uses a **Flat Domain DAG**:
58
58
  - `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu-queue` / `menu-settings` / `menu` / `commands`: model identity, thinking levels, scoped model handling, menu render/callback behavior, slash commands, bot commands, and interactive controls.
59
59
  - `sections`: Telegram menu-section registry, opaque section callback tokens, render/callback dispatch, safe section ports, and diagnostics.
60
60
  - `keyboard`: shared inline-keyboard reply-markup shape only; feature domains own labels, callback data, and behavior.
61
- - `preview` / `replies` / `rendering`: streaming preview lifecycle, final reply delivery, reply parameters, Telegram HTML rendering, chunking, and stable preview snapshots.
61
+ - `preview` / `replies` / `rendering`: throttled native Rich Markdown draft delivery and fallback preview transport, native final reply delivery, reply parameters, transport-limit chunking, and remaining Telegram HTML rendering for bridge-owned UI/compatibility surfaces.
62
62
  - `outbound-markup`: top-level assistant action comment parsing, attribute parsing, voice reply planning, and preview/delivery stripping.
63
63
  - `outbound`: outbound text transformations, voice/button artifact delivery, and generated callback actions.
64
64
  - `outbound-attachments`: `telegram_attach`, queued outbound files, stat/limit checks, and photo/document delivery classification.
@@ -117,7 +117,7 @@ Deleting `locks.json` resets runtime ownership without deleting Telegram configu
117
117
 
118
118
  1. Poll updates through `getUpdates`.
119
119
  2. Persist update offsets only after successful handling; repeated handler failures are bounded.
120
- 3. Filter to the paired private user.
120
+ 3. Filter to the paired private user; guest-mode updates require an existing paired user and cannot establish first pairing.
121
121
  4. Dispatch owned callbacks and controls before fallback prompt forwarding.
122
122
  5. Coalesce media groups and likely split long text when needed.
123
123
  6. Download files into `~/.pi/agent/tmp/telegram` with size limits and partial-download cleanup.
@@ -191,17 +191,19 @@ During active Telegram-owned turns, assistant message start/update hooks re-arm
191
191
 
192
192
  ### Rendering And Delivery
193
193
 
194
- Telegram replies are rendered as Telegram HTML, not raw Markdown. The renderer is Telegram-specific and regression-prone.
194
+ Assistant replies use Telegram-native Rich Markdown. Final Markdown is sent directly as `InputRichMessage.markdown` through `sendRichMessage`, streaming previews use `sendRichMessageDraft` when draft delivery is available, and editable fallback previews are finalized through `editMessageText.rich_message`. Guest replies also use native Rich Markdown through `InputRichMessageContent` in `answerGuestQuery` results. The bridge still strips top-level assistant action comments before delivery and may split output only for Telegram transport limits.
195
195
 
196
- Key guarantees:
196
+ Assistant delivery guarantees:
197
197
 
198
- - Real code blocks stay literal and escaped.
199
- - Supported absolute links stay clickable; unsupported links degrade safely.
200
- - Markdown tables render as compact monospace blocks and count grapheme/display width.
201
- - Lists, task lists, quotes, headings, and blank-line spacing have Telegram-specific preservation rules.
202
- - Long replies are chunked below Telegram limits with balanced HTML where possible.
203
- - Streaming previews prefer stable rich blocks and append the unstable tail conservatively as readable plain text.
204
- - Preview flushes are serialized so older edits cannot race newer snapshots.
198
+ - Model-authored Markdown is the source of truth; the bridge does not pre-render assistant Markdown to HTML.
199
+ - Long native Markdown replies are split only at Telegram Rich Message transport limits.
200
+ - Streaming previews pass assistant Markdown through to `sendRichMessageDraft` with ownership checks, voice suppression, serialized flushes, and an editable plain-message fallback when draft delivery is unavailable.
201
+ - Preview flushes are serialized so older edits cannot race newer drafts; final delivery waits for active draft flushes and does not perform a post-final draft-clear call.
202
+
203
+ UI/compat rendering guarantees:
204
+
205
+ - Bridge-owned UI surfaces such as commands, menus, status messages, queue controls, and interactive sections use Telegram HTML/plain rendering helpers by default. These texts are authored for Telegram UI rather than model output, so explicit HTML markup remains clearer and easier to maintain.
206
+ - In those UI/compat surfaces, real code blocks stay literal and escaped, supported absolute links stay clickable, unsupported links degrade safely, tables use compact monospace rendering with grapheme/display-width accounting, and list/quote/heading spacing stays Telegram-safe.
205
207
 
206
208
  Final delivery attaches reply metadata only where requested. Reply parameters apply only to the first chunk of split messages; continuation chunks are adjacent normal messages. Media-group turns reply to the representative message id.
207
209
 
@@ -214,7 +216,7 @@ Assistant-authored final-message actions use hidden top-level comments:
214
216
  - `telegram_voice` creates voice reply artifacts through configured outbound handlers, programmatic voice handlers, or registered synthesis providers.
215
217
  - `telegram_button` creates inline buttons whose callbacks enqueue the configured prompt text as a normal Telegram prompt turn.
216
218
 
217
- Preview rendering strips top-level action comments while streaming. Comments inside code fences, quotes, lists, or indented examples stay literal.
219
+ Preview delivery strips top-level action comments before streaming draft Markdown. Comments inside code fences, quotes, lists, or indented examples stay literal.
218
220
 
219
221
  Unknown callback data outside owned prefixes is forwarded as `[callback] <data>` only after built-in and extension handlers decline it.
220
222
 
package/docs/outbound.md CHANGED
@@ -22,9 +22,9 @@ Configured text handlers provide `template`. A string is one command; an array i
22
22
 
23
23
  ## Text Handler Config
24
24
 
25
- `type: "text"` handlers transform final text replies before rendering and delivery. The source text is provided on stdin and as `{text}`. Successful non-empty stdout replaces the current text. Empty stdout or handler failure keeps the previous text and records diagnostics.
25
+ `type: "text"` handlers transform final text replies before native Rich Markdown delivery. The source text is provided on stdin and as `{text}`. Successful non-empty stdout replaces the current text. Empty stdout or handler failure keeps the previous text and records diagnostics.
26
26
 
27
- This is ideal for machine translation, tone normalization, redaction, glossary expansion, compliance footers, or any other final text rewrite that should be configured outside the agent prompt. Text handlers run before Markdown/HTML rendering, so a Markdown reply remains Markdown input to the handler. They also run when the bridge finalizes an already streamed rich preview; in that path Telegram can briefly show a pre-transform preview before the final edited message is replaced with the handler output. Inline buttons are built as reply markup: visible button labels pass through the same text handler, while callback data and callback prompts remain unchanged.
27
+ This is ideal for machine translation, tone normalization, redaction, glossary expansion, compliance footers, or any other final text rewrite that should be configured outside the agent prompt. Text handlers run before native Rich Markdown delivery, so a Markdown reply remains Markdown input to the handler. They also run when the bridge finalizes an already streamed rich preview; in that path Telegram can briefly show a pre-transform preview before the final Rich Message reply replaces it. Inline buttons are built as reply markup: visible button labels pass through the same text handler, while callback data and callback prompts remain unchanged.
28
28
 
29
29
  Simple machine-translation handler with explicit text placeholder:
30
30
 
@@ -99,6 +99,8 @@ Hidden/default semantics are represented by absence:
99
99
  - Voice Reply `hidden`: no `voice.replyMode` key is persisted.
100
100
  - Time Injection `hidden`: no `time.injectionMode` key is persisted; if `time` becomes empty, the whole `time` object may be omitted.
101
101
 
102
+ Assistant Markdown delivery is native: final replies are sent as `InputRichMessage.markdown` via `sendRichMessage`, draft previews use `sendRichMessageDraft` when available, and fallback preview messages are finalized with `editMessageText.rich_message` when they can be edited in place. Guest replies use `InputRichMessageContent` in `answerGuestQuery` results. Bridge-owned UI surfaces such as menus, status, queue controls, commands, and sections keep explicit Telegram HTML/plain rendering by default because those texts are authored by the bridge or companion extensions for Telegram UI. Companion extension sections may explicitly request `"markdown"`, `"html"`, or `"plain"` per view. There is no `telegram.json` rendering toggle for assistant delivery. The bridge sets `skip_entity_detection: true` for assistant and guest Markdown so technical text such as `/commands`, hashtags, URLs, phone numbers, and card-like numbers does not gain unintended automatic entities; explicit Markdown links still belong in the Markdown source.
103
+
102
104
  Environment variables are stable only where documented in the README: bot-token bootstrap, proxy behavior, agent root, and inbound/outbound file size limits.
103
105
 
104
106
  ## Programmatic API Matrix
@@ -327,6 +329,7 @@ export default function demoSection(pi: ExtensionAPI) {
327
329
  order: 50,
328
330
  render: () => ({
329
331
  text: "<b>Demo section</b>\n\nThis section was rendered by a companion extension.",
332
+ parseMode: "html",
330
333
  replyMarkup: { inline_keyboard: [] },
331
334
  }),
332
335
  });
package/docs/sections.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Telegram Extension Sections let ordinary pi extensions add structured UI surfaces to the `pi-telegram` inline application menu. The platform mirrors π's own extensibility model: small, composable extensions that plug into a shared shell without owning transport, polling, authorization, or menu lifecycle.
12
12
 
13
- `pi-telegram` stays the single bot operator. Extensions register typed sections; the bridge handles rendering, callback routing, token mapping, navigation hierarchy, and diagnostics. No second polling loop, no new loader — just one `registerTelegramSection()` call.
13
+ `pi-telegram` stays the single bot operator. Extensions register typed sections; the bridge handles Telegram UI rendering, callback routing, token mapping, navigation hierarchy, and diagnostics. Section views default to explicit Telegram HTML UI markup, while extensions can request Markdown or plain text when that better matches their content. No second polling loop, no new loader — just one `registerTelegramSection()` call.
14
14
 
15
15
  ## 2. Contract Layers
16
16
 
@@ -111,7 +111,9 @@ interface TelegramSectionRegistration {
111
111
 
112
112
  interface TelegramSectionView {
113
113
  text: string;
114
- parseMode?: "html" | "plain";
114
+ // Defaults to "html" for explicit Telegram UI markup.
115
+ // Use "markdown" when the section naturally owns Markdown content.
116
+ parseMode?: "markdown" | "html" | "plain";
115
117
  replyMarkup?: TelegramInlineKeyboardMarkup;
116
118
  }
117
119
  ```