@llblab/pi-telegram 0.9.4 → 0.9.6

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
@@ -7,10 +7,11 @@
7
7
  - `Boundary Clarity`: Separate Telegram transport concerns, π integration concerns, rendering behavior, and release/documentation state
8
8
  - `Progressive Enhancement + Graceful Degradation`: Prefer behavior that upgrades automatically when richer runtime context exists, but always preserves a useful fallback path when it does not
9
9
  - `Runtime Safety`: Prefer queue and rendering behavior that fails predictably over clever behavior that can desynchronize the Telegram bridge from π session state
10
+ - `Pi-Native Extensibility`: `pi-telegram` should inherit π's own extension philosophy. It is not only a Telegram adapter; it should become a small, convenient, composable Telegram shell for π extensions, where new capabilities plug into stable contracts instead of forking polling, transport, or menu ownership.
10
11
 
11
12
  ## 1. Concept
12
13
 
13
- `pi-telegram` is a π extension that turns a Telegram DM into a session-local frontend for π, including text/file forwarding, streaming previews, queued follow-ups, model controls, and outbound attachment delivery.
14
+ `pi-telegram` is a Telegram Runtime Adapter for π: a session-local operator console that turns a private Telegram DM into a runtime surface for prompt intake, streaming previews, queue management, model/thinking/settings controls, inbound/outbound handler pipelines, voice/buttons, artifacts, and extension callback interop. Treat it as a Telegram membrane around π, not a narrow message pipe.
14
15
 
15
16
  ## 2. Identity & Naming Contract
16
17
 
@@ -27,7 +28,7 @@
27
28
  - `/tests/*.test.ts`: Domain-mirrored regression suites that follow the same flat naming as `/lib`
28
29
  - `/docs/README.md`: Documentation index for technical project docs
29
30
  - `/docs/architecture.md`: Runtime and subsystem overview for the bridge
30
- - `/README.md`: User-facing project entry point, install guide, and fork summary
31
+ - `/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.
31
32
  - `/AGENTS.md`: Durable engineering and runtime conventions
32
33
  - `/BACKLOG.md`: Canonical open work
33
34
  - `/CHANGELOG.md`: Completed delivery history
@@ -121,6 +122,8 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
121
122
  - When Telegram-visible behavior changes, sync `README.md` and the relevant `/docs` entry in the same pass
122
123
  - When durable runtime constraints or repeat bug patterns emerge, record them here instead of burying them in changelog prose
123
124
  - When fork identity changes, keep `README.md`, package metadata, and docs aligned so the published package does not point back at stale upstream coordinates
125
+ - README positioning should emphasize `/start` as the primary Telegram operator menu and keep reaction shortcuts secondary. Reactions are useful queue affordances, but menu controls are the core CLI-to-Telegram bridge.
126
+ - Document configuration knobs without UI in the root README when they affect bootstrap, networking, or transport limits; currently this includes token env bootstrap, Node env proxy mode, and inbound/outbound size limits.
124
127
  - Work only inside this repository during development tasks; updating the installed Pi extension checkout is a separate manual operator step, not part of normal in-repo implementation work
125
128
 
126
129
  ## 8. Integration Protocols
package/BACKLOG.md CHANGED
@@ -2,4 +2,13 @@
2
2
 
3
3
  ## Open Work
4
4
 
5
- No open work.
5
+ - Implement Telegram Extension Sections Platform for the 0.10.0 line.
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.
8
+ - Priority: Low.
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
+ - 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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.6: Runtime Adapter Positioning
4
+
5
+ - `[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.
6
+ - `[Telegram API]` Introduced `TELEGRAM_API_BASE` for the Bot API endpoint and documented native HTTP/HTTPS proxy operation through `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, and explicit `NODE_USE_ENV_PROXY=1` / `--use-env-proxy` enablement. Impact: users behind corporate proxies, local HTTP tunnels, or restricted networks get a zero-runtime-dependency proxy path without replacing native `fetch`; SOCKS5 remains outside the zero-dependency core.
7
+ - `[Dependencies]` Refreshed the lockfile transitive dependency set so `npm audit` clears current `fast-uri` and `fast-xml-builder` advisories inherited through development peer installs. Impact: the full `npm run validate` pipeline passes without changing runtime dependencies.
8
+ - `[README]` Restructured the user entrypoint around install → connect → use → core features → docs, then consolidated examples, terminology, proxy setup, `PI_CODING_AGENT_DIR`, and other environment-only configuration around the runtime-adapter/operator-console model. Impact: first-time users get a clearer path from installation to operation, while vivid examples and non-UI runtime knobs stay discoverable.
9
+ - `[Context]` Promoted the runtime-adapter/operator-console README rhythm, `/start` menu emphasis, and environment-only configuration rule into `AGENTS.md`. Impact: future documentation edits preserve the same positioning and env-knob coverage instead of drifting back toward a narrow bridge metaphor.
10
+
11
+ ## 0.9.5: Telegram Delivery Resilience Hotfix
12
+
13
+ - `[Preview Delivery]` Preview flush failures from Telegram transport errors such as `fetch failed` / `ECONNRESET` are now caught and recorded as runtime diagnostics instead of escaping from the preview pipeline. Impact: transient Telegram connectivity failures no longer crash the extension during streamed preview edits.
14
+ - `[Final Delivery]` Final Markdown preview replacement now catches Telegram transport failures and returns a normal fallback signal; the agent-end delivery path records final-text delivery failures and continues cleanup, attachment handling, and queue dispatch. Impact: a failed `editMessageText` at `agent_end` no longer breaks the bridge lifecycle or blocks the next queued Telegram turn.
15
+ - `[Diagnostics]` Preview and final delivery failures now flow through the runtime event recorder with compact phase metadata. Impact: `/telegram-status` can show recent transport failures without dumping noisy stack traces into the extension runner.
16
+ - `[Tests]` Added preview and queue regressions for non-fatal Telegram transport failures during preview flush and final delivery.
17
+ - `[Extension Sections Draft]` Added a draft design note for pi-native Telegram extension sections, reserved the future `section:` callback prefix, linked the draft from docs, and recorded the project philosophy that `pi-telegram` should inherit π's extensibility model as a shared Telegram shell for loaded extensions. Impact: the future 0.10.0 extension platform direction is documented without exposing a stable API yet.
18
+ - `[Docs Formatting]` Normalized project Markdown so prose paragraphs stay as single logical lines and Markdown tables remain narrow instead of using artificial hard wraps. Impact: editors and viewers can handle visual wrapping naturally while fixed-width structures stay readable.
19
+ - `[Settings Copy]` Tightened the proactive-push settings text by removing redundant persistence/default wording.
20
+
3
21
  ## 0.9.4: Temp Dir And Command Template Hotfix
4
22
 
5
23
  - `[Telegram Temp Dir]` Default Telegram API temp files now respect `PI_CODING_AGENT_DIR`, falling back to `~/.pi/agent` when the env var is unset. Impact: sandboxed or relocated agent dirs no longer force Telegram downloads through the default home-directory path.
package/README.md CHANGED
@@ -2,24 +2,11 @@
2
2
 
3
3
  ![pi-telegram screenshot](screenshot.png)
4
4
 
5
- This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008460b6c1ca036d92322f69d87f626be0fc`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
6
-
7
- ## Start Here
5
+ **Telegram Runtime Adapter for π.**
8
6
 
9
- - [Project Context](./AGENTS.md)
10
- - [Open Backlog](./BACKLOG.md)
11
- - [Changelog](./CHANGELOG.md)
12
- - [Documentation](./docs/README.md)
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.
13
8
 
14
- ## Key Features
15
-
16
- - **Telegram Controls**: `/start` opens the inline application menu with command help, available π prompt templates, status rows, model, thinking, and queue sections; the Status row reports `compacting` during Telegram `/compact`; `/stop`, `/abort`, `/next`, and `/continue` provide queue-clear, queue-preserve, force-next, and queued-resume semantics respectively; model-switch continuation turns still use the control lane when a restart needs to resume safely.
17
- - **Interactive UI**: Manage your session directly from Telegram. Inline buttons expose an application menu for switching models, choosing model pages from the pagination indicator, adjusting reasoning (thinking) levels, and inspecting or mutating the waiting queue; model scope/pagination controls stay at the top of the model menu, the Queue button shows the current item count, and command emoji are reused on matching controls such as model and thinking.
18
- - **In-flight Model Switching**: Change the active model mid-generation. The agent gracefully pauses, applies the new model, and restarts its response without losing context.
19
- - **Smart Message Queue**: Messages sent while the agent is busy are queued and previewed in the π status bar, and queued turns can be reprioritized or removed with Telegram reactions or the queue section of the inline application menu.
20
- - **Mobile-Optimized Rendering**: Tables and lists are formatted for narrow screens, table padding accounts for emoji grapheme and wide Unicode display width, and Telegram-originated runs prompt the assistant to prefer narrow table columns for phone readability. Markdown is correctly parsed and split to fit Telegram's limits without breaking HTML structures or code blocks, block spacing stays faithful to the original Markdown with readable heading separation, supported absolute links stay clickable, and unsupported link forms degrade safely.
21
- - **File Handling & Attachments**: Send images and files to the agent, transcribe or transform inbound text/media with configured inbound handlers, or ask π to generate and return artifacts. Inbound downloads and outbound attachments are size-limited by default, and outbound files are delivered automatically via the `telegram_attach` tool.
22
- - **Streaming Responses**: Closed Markdown blocks stream back as rich Telegram HTML while π is generating, and the still-growing tail stays readable until the final fully rendered reply lands.
9
+ This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008460b6c1ca036d92322f69d87f626be0fc`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
23
10
 
24
11
  ## Install
25
12
 
@@ -35,22 +22,16 @@ From git:
35
22
  pi install git:github.com/llblab/pi-telegram
36
23
  ```
37
24
 
38
- ## Configure
25
+ ## Connect
39
26
 
40
- ### Configuration Philosophy
41
-
42
- The extension intentionally keeps rich visual/TUI configuration minimal for now. Rich setup screens may arrive later, but they are not the main configuration surface yet.
43
-
44
- For advanced setup, ask an agent to read this `README.md` and the docs, then update `~/.pi/agent/telegram.json` for your workflow. Agents are good at small configuration changes, and this keeps the bridge simple while handler pipelines and operator preferences continue to evolve.
45
-
46
- ### 1. Telegram Bot
27
+ ### 1. Create a Telegram bot
47
28
 
48
29
  1. Open [@BotFather](https://t.me/BotFather)
49
30
  2. Run `/newbot`
50
31
  3. Pick a name and username
51
32
  4. Copy the bot token
52
33
 
53
- ### 2. Configure the extension in π
34
+ ### 2. Configure the bot token in π
54
35
 
55
36
  Start π, then run:
56
37
 
@@ -66,63 +47,110 @@ Paste your bot token when prompted. If a bot token is already saved in `~/.pi/ag
66
47
  /telegram-connect
67
48
  ```
68
49
 
69
- The bridge is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, while `/new` and same-`cwd` process restarts resume automatically.
50
+ The adapter is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, while `/new` and same-`cwd` process restarts resume automatically.
70
51
 
71
- ### 4. Pair your account from Telegram
52
+ ### 4. Pair your Telegram account
72
53
 
73
54
  1. Open the DM with your bot in Telegram
74
55
  2. Send `/start`
75
56
 
76
- The first user to message the bot becomes the exclusive owner of the bridge. The extension will only accept messages from this user.
57
+ The first user to message the bot becomes the exclusive owner of the adapter. Messages from other users are ignored.
77
58
 
78
- ## Usage
59
+ ### Environment-only configuration
79
60
 
80
- Once paired, simply chat with your bot in Telegram. All text, images, and files are forwarded to π.
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:
81
62
 
82
- ### Telegram Commands & Controls
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`.
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
+ - **Inbound file limit**: `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES` changes the default 50 MiB Telegram download limit.
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.
83
68
 
84
- Use these inside the Telegram DM with your bot:
69
+ Proxy example:
85
70
 
86
- - **`/start`**: Pair the first Telegram user when needed, register bridge bot commands, and open the inline application menu with command help, available π prompt templates, status rows, and controls.
87
- - **`/compact`**: Start session compaction (only works when the session is idle).
88
- - **`/next`**: Dispatch the next queued turn (aborts π first if busy).
89
- - **`/continue`**: Enqueue a priority `continue` prompt. It waits like normal Telegram work when π is busy and can trigger prompt/skill handling that listens for `continue`.
90
- - **`/stop`**: Abort the active run and clear all waiting Telegram queue items.
91
- - **`/abort`**: Abort the active run without touching the queue.
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
+ ## Use
83
+
84
+ 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.
85
+
86
+ What it feels like:
87
+
88
+ - Open `/start` and get a Telegram control panel for the running π session: status, prompt templates, model, thinking, settings, and queue.
89
+ - Fire off three tasks while π is busy. They become visible queue items instead of terminal noise.
90
+ - Open Queue from the menu, inspect waiting work, delete stale prompts, or move important work forward.
91
+ - Switch models from Telegram mid-run; the adapter schedules a safe continuation instead of tearing state apart.
92
+ - Send a voice note; an inbound handler transcribes it; π answers in the same chat.
93
+ - Drop a screenshot and ask, "what is broken here?" The image payload reaches π with the local file context.
94
+ - Ask for a generated file; when π calls `telegram_attach`, the artifact returns to Telegram with the next reply.
92
95
 
93
- Prompt-template commands: π prompt templates are mapped to Telegram-safe aliases (`fix-tests.md` becomes `/fix_tests`) and shown as compact command-only rows between the built-in commands and status rows in `/start`. They are not registered in the Telegram bot command menu, keeping the bot menu focused on bridge controls. Sending `/template_name args` from Telegram expands the matching π prompt-template file and queues the expanded prompt like normal Telegram work.
96
+ ### Telegram controls
94
97
 
95
- Hidden compatibility shortcuts: `/help` and `/status` open the same main application menu, `/model` opens the model section, `/thinking` opens the thinking section, `/queue` opens the queue section, and `/settings` opens hidden bridge settings. Settings rows open detail submenus with Back plus green/black/yellow option controls such as On and Off for checkboxes. These shortcuts are intentionally not shown in the bot command menu.
98
+ Use these inside the Telegram DM with your bot. The main entrypoint is `/start`: it opens the operator menu and exposes many of the important agent controls that normally live in the CLI, adapted for Telegram.
96
99
 
97
- Telegram command admission is explicit: `/compact`, `/queue`, `/settings`, `/stop`, `/abort`, `/next`, `/help`, `/start`, `/status`, `/model`, and `/thinking` execute immediately. `/continue` is a command shortcut that enqueues a priority Telegram prompt containing `continue`. Prompt-template commands expand before queueing and then follow normal prompt-queue rules. Synthetic model-switch continuation turns still enter the high-priority control lane so they can resume before normal queued prompts when π becomes safe to dispatch.
100
+ - **`/start`**: Pair the first Telegram user when needed, register bot commands, and open the inline application menu with command help, prompt-template commands, status rows, model controls, thinking controls, settings, and queue controls.
101
+ - **`/compact`**: Start session compaction when the session is idle.
102
+ - **`/next`**: Dispatch the next queued turn, aborting π first if needed.
103
+ - **`/continue`**: Enqueue a priority `continue` prompt.
104
+ - **`/abort`**: Abort the active run without touching the queue.
105
+ - **`/stop`**: Abort the active run and clear waiting Telegram queue items.
106
+
107
+ Hidden compatibility shortcuts: `/help` and `/status` open the main application menu, `/model` opens model controls, `/thinking` opens reasoning controls, `/queue` opens queue controls, and `/settings` opens bridge settings.
98
108
 
99
- ### Pi Commands
109
+ Prompt-template commands are discovered from π prompt templates, mapped to Telegram-safe aliases (`fix-tests.md` becomes `/fix_tests`), shown in `/start`, and expanded before queueing.
110
+
111
+ ### π commands
100
112
 
101
113
  Run these inside π, not Telegram:
102
114
 
103
115
  - **`/telegram-setup`**: Configure or update the Telegram bot token.
104
- - **`/telegram-status`**: Check bridge status, connection, polling, execution, queue, and recent redacted runtime/API failure events.
105
- - **`/telegram-settings`**: Open local bridge settings and toggle proactive push using the same `telegram.json` flag as the Telegram `/settings` menu.
106
- - **`/telegram-connect`**: Start polling Telegram updates in the current π session, acquire the singleton lock, or interactively move ownership here from another live instance.
116
+ - **`/telegram-connect`**: Start polling Telegram updates in the current π session and acquire the singleton lock.
117
+ - **`/telegram-settings`**: Open local settings and toggle proactive push using the same `telegram.json` flag as the Telegram `/settings` menu.
107
118
  - **`/telegram-disconnect`**: Stop polling in the current π session and release the singleton lock.
119
+ - **`/telegram-status`**: Inspect adapter status, connection, polling, execution, queue, and recent redacted runtime/API failure events.
120
+
121
+ ### Files and artifacts
122
+
123
+ Send files or images directly to the bot. Inbound downloads are saved under `<agent-dir>/tmp/telegram` and default to a 50 MiB limit. The agent dir is `~/.pi/agent` unless `PI_CODING_AGENT_DIR` overrides it.
124
+
125
+ If you ask π for a generated file, π can call the `telegram_attach` tool and the adapter sends the file with the next Telegram reply. Outbound attachments also default to a 50 MiB limit. Environment variables for both limits are listed in [Environment-only configuration](#environment-only-configuration).
126
+
127
+ ## Core features
128
+
129
+ ### Operator menu and controls
108
130
 
109
- ### Queue, Reactions, and Media
131
+ The inline application menu is the primary operator surface. It exposes status, prompt-template commands, model selection, thinking level selection, settings, and queue inspection/mutation: a Telegram-shaped subset of the important handles normally available from the CLI. A typical control loop stays inside Telegram: open `/start`, inspect status, jump into Queue, delete stale work, switch model, return to the main menu, and keep the π session running without touching the terminal.
110
132
 
111
- - If you send more Telegram messages while π is busy, they enter the default prompt queue and are processed in order.
112
- - Very long text messages that Telegram appears to split automatically are coalesced through a short conservative debounce and forwarded to π as one prompt when the first chunk is near Telegram's text limit, currently using a 3600-character threshold. Commands, bot messages, media groups, and normal short follow-ups are not coalesced.
113
- - `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` move a waiting prompt into the priority prompt queue, behind control actions but ahead of default prompts. Removing the last priority reaction sends it back to its normal queue position, and adding a priority reaction again gives it a fresh priority position.
114
- - `👎`, `👻`, `💔`, `💩`, and `🗑` remove a waiting turn from the queue. Telegram Bot API does not expose ordinary DM message-deletion events through the polling path used here, so queue removal is bound to removal reactions.
115
- - Reactions apply to any waiting Telegram turn, including text, voice, files, images, and media groups. For media groups, a reaction on any message in the group applies to the whole queued turn.
116
- - If you edit a Telegram message while it is still waiting in the queue, the queued turn is updated instead of creating a duplicate prompt. Edits after a turn has already started may not affect the active run.
117
- - Telegram replies to earlier text or caption messages are forwarded as `[reply]` context for normal prompts, while slash commands still parse from the new message text only.
118
- - Inbound images, albums, and files are saved to `~/.pi/agent/tmp/telegram`. Unhandled local file paths are included in the prompt, handled attachment output is injected into the prompt text, and inbound images are forwarded to π as image inputs. Inbound downloads default to a 50 MiB limit and can be adjusted with `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES`.
119
- - Queue reactions depend on Telegram delivering `message_reaction` updates for your bot and chat type.
133
+ ### Queue runtime
120
134
 
121
- ### Inbound Handlers
135
+ 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.
122
136
 
123
- `telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to the paired Telegram chat when no Telegram turn is active. Local prompt text is not sent because the bot does not own or mirror terminal user messages. The mode is off by default, can be toggled from the hidden `/settings` menu, persists across contexts until explicitly disabled or removed from config, is gated by the current Telegram lock owner, and skips aborted or failed turns.
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 set intentionally includes common default reactions first; premium-only reactions such as `🗑` are optional convenience, not the core queue UI. The same rules apply to text, voice, files, images, and media groups.
124
138
 
125
- `telegram.json` can define ordered `inboundHandlers` for Telegram π preprocessing such as text translation, voice transcription, OCR, or PDF extraction. Matching handlers run before the Telegram turn enters the π queue. If a matching media/file handler fails, the next matching handler is tried as a fallback. Legacy `attachmentHandlers` still work as a deprecated compatibility alias and are appended after `inboundHandlers`.
139
+ ### Streaming and Telegram HTML rendering
140
+
141
+ 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.
142
+
143
+ 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.
144
+
145
+ ### Media, replies, edits, and split text
146
+
147
+ Telegram replies to earlier text or caption messages are forwarded as `[reply]` context for normal prompts, while slash commands still parse from the new message text only. If a Telegram message is edited while still waiting in the queue, the queued turn is updated instead of duplicated. Very long text messages that Telegram appears to split automatically are coalesced through a conservative debounce when the first chunk is near Telegram's text limit.
148
+
149
+ ### Inbound handlers
150
+
151
+ `telegram.json` can define ordered `inboundHandlers` for Telegram → π preprocessing: text translation, voice transcription, OCR, PDF extraction, or any command-template pipeline. Matching handlers run before the turn enters the queue; failed handlers record diagnostics and fall back safely. Legacy `attachmentHandlers` still work as a deprecated compatibility alias appended after `inboundHandlers`.
152
+
153
+ A practical voice setup is simple: Telegram `.ogg` arrives, STT runs locally or through your chosen command, stdout is injected as `[outputs]`, and π receives the result as usable prompt context.
126
154
 
127
155
  ```json
128
156
  {
@@ -149,19 +177,9 @@ Run these inside π, not Telegram:
149
177
  }
150
178
  ```
151
179
 
152
- Matching supports optional `mime`, `type`, or `match`; `mime` can be used without `type`, and wildcards like `audio/*` or `text/*` are accepted. Raw Telegram text can match `type: "text"`, `mime: "text/plain"`, or `mime: "text/*"`; it is passed on stdin and as `{text}`, and non-empty stdout replaces the prompt text. Media/file handlers receive `{file}`, `{mime}`, and `{type}`; local attachments stay in the prompt under `[attachments] <directory>` with relative file entries, and successful media/file handler stdout is added under `[outputs]`. Attached `text/plain`/`text/*` files have a built-in fail-open reader that injects UTF-8 content into `[outputs]` when no configured handler produced output. Failed handlers record diagnostics and fall back safely. The portable command-template contract is documented in [`docs/command-templates.md`](./docs/command-templates.md); Telegram-specific inbound config is documented in [`docs/inbound-handlers.md`](./docs/inbound-handlers.md).
153
-
154
- ### Requesting Files
180
+ ### Outbound handlers, voice, and buttons
155
181
 
156
- If you ask π for a file or generated artifact (e.g., _"generate a shell script and attach it"_), π can call the `telegram_attach` tool, and the extension will send the file alongside its next Telegram reply. `telegram_attach` is the only π tool registered by `pi-telegram`; use it for ordinary files, not for Telegram-native voice or buttons. Outbound attachments default to a 50 MiB limit and can be adjusted with `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES`.
157
-
158
- ### Assistant-Authored Outbound Actions
159
-
160
- Assistant replies can include hidden outbound blocks. `telegram_voice` and `telegram_button` are not π tools; they are assistant-authored HTML comments that the bridge removes from Telegram text and handles after `agent_end`. Recognized blocks must start at column zero on a top-level line outside fenced code, quotes, and lists, so documentation examples remain literal. The agent writes normal Markdown; the extension owns voice generation, button markup, callback routing, and delivery.
161
-
162
- #### Voice
163
-
164
- Voice blocks synthesize their text and upload it as a native Telegram `sendVoice` OGG/Opus message. Use body form for multiline text, `text="..."` for explicit one-line text with optional attributes, and the colon shorthand for a one-line voice with no attributes. The spoken text may be a concise companion summary, but it does not have to follow that format; write what you want spoken and keep it TTS-friendly:
182
+ Assistant replies can include hidden outbound blocks. `telegram_voice` and `telegram_button` are not π tools; they are assistant-authored HTML comments that the adapter removes from Telegram text and handles after `agent_end`. Recognized blocks must start at column zero on a top-level line outside fenced code, quotes, and lists.
165
183
 
166
184
  ```md
167
185
  Full technical answer stays readable as text.
@@ -170,27 +188,14 @@ Full technical answer stays readable as text.
170
188
  Text to synthesize as a Telegram voice message.
171
189
  -->
172
190
 
173
- <!-- telegram_voice lang=ru rate=+30% text="Short spoken companion summary." -->
174
-
175
- <!-- telegram_voice: Short spoken companion summary. -->
176
- ```
177
-
178
- Outbound `type: "text"` handlers can transform final text/Markdown before Telegram rendering and delivery, using stdin and `{text}` as input and non-empty stdout as replacement text. They are a good fit for machine translation, tone normalization, redaction, glossary expansion, or any other final text rewrite that should happen outside the agent prompt. The transform also applies when the bridge finalizes an already streamed rich preview, so Telegram may briefly show the pre-transform preview before the final edited message lands. Inline button labels are transformed too, while callback data and prompts stay unchanged.
179
-
180
- ```json
181
- {
182
- "outboundHandlers": [
183
- {
184
- "type": "text",
185
- "template": "/path/to/translate --lang {lang=ru} --text {text}"
186
- }
187
- ]
188
- }
191
+ <!-- telegram_button label="Show risks"
192
+ List the main risks first.
193
+ -->
189
194
  ```
190
195
 
191
- Outbound voice is disabled unless a matching `outboundHandlers[]` entry is configured. Multiple `telegram_voice` blocks in one reply are synthesized and sent independently, preserving each block's attributes. The bridge uses the same [command-template contract](./docs/command-templates.md) as inbound handlers: split the template into args, substitute placeholders, execute without a shell, and use stdout as the result channel for a single template.
196
+ Outbound `type: "text"` handlers can transform final text/Markdown before Telegram rendering and delivery. Outbound `type: "voice"` handlers can translate, synthesize, and convert hidden `telegram_voice` text into Telegram-native OGG/Opus voice through the same command-template contract used by inbound handlers.
192
197
 
193
- A composed voice setup can translate the hidden `telegram_voice` text, synthesize it, and convert MP3 to Telegram-native OGG/Opus in one pipeline. The bridge provides `{text}`, `{mp3}`, and `{ogg}` to every step; top-level `args`/`defaults` apply to all steps unless a step defines private values, the default command timeout applies automatically, and each step's stdout is passed to the next step's stdin by default. Use `"output": "ogg"` when the artifact path should come from the generated `{ogg}` value instead of final stdout:
198
+ A composed voice pipeline can translate, synthesize, and convert in one pass:
194
199
 
195
200
  ```json
196
201
  {
@@ -208,43 +213,36 @@ A composed voice setup can translate the hidden `telegram_voice` text, synthesiz
208
213
  }
209
214
  ```
210
215
 
211
- #### Buttons
216
+ The agent writes intent; the adapter owns transport. Text remains readable, voice becomes native Telegram media, and buttons route back as queued prompts.
212
217
 
213
- Button blocks attach inline quick replies to the final text. Use one independent `telegram_button` block per action. If the prompt should equal the label, use the colon shorthand. If the prompt differs, use the inline `prompt="..."` attribute for one-line prompts or the body form for multiline prompts:
218
+ ### Extension interop
214
219
 
215
- ```md
216
- I can continue.
220
+ Unknown inline-button callbacks are forwarded to π as `[callback] <data>` when they do not belong to pi-telegram, so other extensions can namespace and handle Telegram buttons without polling the bot themselves. Layered extensions that need synchronous update handling can register a runtime interceptor on the shared update registry.
217
221
 
218
- <!-- telegram_button label="Show risks"
219
- List the main risks first.
220
- -->
222
+ ### Proactive push
221
223
 
222
- <!-- telegram_button label=Continue prompt="Continue with the current plan." -->
224
+ `telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to the paired Telegram chat when no Telegram turn is active. Local prompt text is not mirrored because the bot does not own terminal user messages. The mode is off by default and can be toggled from settings.
223
225
 
224
- <!-- telegram_button: OK -->
225
- ```
226
+ ## Docs
226
227
 
227
- Button prompts are routed back into the normal Telegram queue as prompt turns. Keep the opening comment unclosed until the body-ending `-->` for body-form buttons. Closed heads must use `prompt="..."` or the colon shorthand to create a button. Unknown inline-button callbacks that do not belong to pi-telegram are forwarded to π as `[callback] <data>` so other extensions can namespace and handle their own Telegram buttons without polling the bot themselves; see the [Callback Namespace Standard](./docs/callback-namespaces.md). Layered extensions that need to react to Telegram updates synchronously inside their own runtime (for example, to resolve a blocking-tool approval Promise the moment a callback arrives) can register a runtime interceptor on the shared update registry; see [External Handlers](./docs/external-handlers.md). Outbound handler details are documented in [`docs/outbound-handlers.md`](./docs/outbound-handlers.md).
228
-
229
- ## Streaming
230
-
231
- The extension streams assistant previews back to Telegram while π is generating.
232
-
233
- Rich previews are sent through editable messages because Telegram drafts are text-only. Closed top-level Markdown blocks can appear with formatting before the answer finishes, while the still-growing tail remains conservative and readable until the preview is replaced with the fully rendered Telegram HTML reply. Editable preview messages are also attached as replies to the source Telegram prompt when possible.
234
-
235
- ## Status bar
236
-
237
- The π status bar shows the current bridge state plus queued Telegram turns as compact previews. Busy labels distinguish states such as `active`, `dispatching`, `queued`, `tool running`, `model`, and `compacting`. Telegram prompt guidance asks agents to keep tables, dense list items, and compact text blocks within about 37 visible cells when possible so mobile replies stay readable.
238
-
239
- ```text
240
- telegram active +3
241
- ```
228
+ - [Project Context](./AGENTS.md): durable engineering conventions and architecture constraints.
229
+ - [Open Backlog](./BACKLOG.md): planned work and known follow-ups.
230
+ - [Changelog](./CHANGELOG.md): completed delivery history.
231
+ - [Documentation Index](./docs/README.md): technical docs hub.
232
+ - [Architecture](./docs/architecture.md): runtime and subsystem overview.
233
+ - [Inbound Handlers](./docs/inbound-handlers.md): Telegram → π preprocessing.
234
+ - [Outbound Handlers](./docs/outbound-handlers.md): final text, voice, and artifact pipelines.
235
+ - [Command Templates](./docs/command-templates.md): portable command-template contract.
236
+ - [Callback Namespaces](./docs/callback-namespaces.md): callback interop for layered extensions.
237
+ - [External Handlers](./docs/external-handlers.md): shared update interception.
238
+ - [Extension Sections Draft](./docs/extension-sections.md): future Telegram extension sections platform.
239
+ - [Locks](./docs/locks.md): singleton polling ownership.
242
240
 
243
241
  ## Notes
244
242
 
245
- - Replies to Telegram prompts are sent as Telegram replies to the source message when possible; if the source message is unavailable, delivery falls back to a normal message
246
- - Long replies are split below Telegram's 4096 character limit without intentionally breaking Telegram HTML formatting; only the first split message is attached as a Telegram reply to the source prompt
247
- - Temporary inbound Telegram files are cleaned up on later session starts
243
+ - The extension intentionally keeps rich visual/TUI configuration minimal for now. For advanced setup, ask an agent to read this README and the docs, then update `~/.pi/agent/telegram.json` for your workflow.
244
+ - Replies to Telegram prompts are sent as Telegram replies to the source message when possible; if the source message is unavailable, delivery falls back to a normal message.
245
+ - Temporary inbound Telegram files are cleaned up on later session starts.
248
246
 
249
247
  ## License
250
248
 
package/docs/README.md CHANGED
@@ -11,3 +11,4 @@ Living index of project documentation in `/docs`.
11
11
  - [locks.md](./locks.md) — Shared `locks.json` standard for singleton extension ownership
12
12
  - [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
13
13
  - [external-handlers.md](./external-handlers.md) — Runtime interceptor registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
14
+ - [extension-sections.md](./extension-sections.md) — Draft Telegram Extension Sections Standard for external menu sections and structured Telegram UI extension points
@@ -23,29 +23,28 @@ Naming rule: because the repository already scopes this codebase to Telegram, ex
23
23
 
24
24
  Current runtime areas use these ownership boundaries:
25
25
 
26
- | Domain | Owns |
27
- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
- | `index.ts` | Single composition root for live π/Telegram ports, session state, API-bound transport adapters, and status updates |
29
- | `api` | Bot API transport shapes/helpers, retries, file download, temp-dir lifecycle, inbound limits, chat actions, lazy bot-token clients, runtime error recording |
30
- | `config` / `setup` | Persisted bot/session pairing state, authorization, first-user pairing, token prompting, env fallback, validation, config persistence |
31
- | `locks` / `polling` | Singleton `locks.json` ownership, takeover/restart semantics, long-poll controller state, update offset persistence, poll-loop runtime wiring |
32
- | `updates` / `routing` | Update classification/execution planning, paired authorization, reactions, edits, callbacks, and inbound route composition |
33
- | `media` / `text-groups` / `turns` / `inbound-handlers` | Text/media extraction, media-group debounce, long-text split coalescing, inbound downloads, inbound text/media handler execution, turn building/editing, image reads, legacy `attachmentHandlers` compatibility |
34
- | `queue` | Queue item contracts, lane admission/order, stores, mutations, dispatch readiness/runtime, prompt/control enqueueing, session and agent/tool lifecycle sequencing |
35
- | `runtime` | Session-local coordination primitives: counters, lifecycle flags, setup guard, abort handler, typing-loop timers, prompt-dispatch flags, agent-end reset binding |
36
- | `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-queue` / `menu-settings` / `commands` | Model identity/thinking levels, scoped model resolution, in-flight switching, model-menu UI, thinking-menu UI, status-menu UI, inline application callback composition, queue-menu UI, hidden settings UI, slash commands, bot command registration |
37
- | `keyboard` | Shared Telegram inline-keyboard reply-markup structure; feature domains own callback semantics and button construction |
38
- | `preview` / `replies` / `rendering` | Preview lifecycle/transports, final reply delivery and reply parameters, Telegram HTML Markdown rendering, chunking, stable-preview snapshots |
39
- | `outbound-handlers` | Outbound text transformation, assistant-authored outbound comments, generated reply artifacts, inline-keyboard callbacks, and post-`agent_end` outbound action delivery |
40
- | `outbound-attachments` | `telegram_attach` registration, outbound attachment queueing, stat/limit checks, photo/document delivery classification |
41
- | `status` | Status-bar/status-message rendering, queue-lane status views, redacted runtime event ring, grouped π diagnostics |
42
- | `lifecycle` / `prompts` / `prompt-templates` / `pi` | π hook registration, Telegram-specific before-agent prompt injection, π prompt-template discovery/expansion, centralized direct pi SDK imports and context adapters |
43
- | `command-templates` | Portable shell-free command-template standard helpers, composition expansion, placeholder substitution, and executable resolution |
26
+ - `index.ts`: single composition root for live π/Telegram ports, session state, API-bound transport adapters, and status updates.
27
+ - `api`: Bot API transport shapes/helpers, retries, file download, temp-dir lifecycle, inbound limits, chat actions, lazy bot-token clients, runtime error recording, and the `TELEGRAM_API_BASE` constant for the Bot API endpoint.
28
+ - `config` / `setup`: persisted bot/session pairing state, authorization, first-user pairing, token prompting, env fallback, validation, and config persistence.
29
+ - `locks` / `polling`: singleton `locks.json` ownership, takeover/restart semantics, long-poll controller state, update offset persistence, and poll-loop runtime wiring.
30
+ - `updates` / `routing`: update classification/execution planning, paired authorization, reactions, edits, callbacks, and inbound route composition.
31
+ - `media` / `text-groups` / `turns` / `inbound-handlers`: text/media extraction, media-group debounce, long-text split coalescing, inbound downloads, inbound text/media handler execution, turn building/editing, image reads, and legacy `attachmentHandlers` compatibility.
32
+ - `queue`: queue item contracts, lane admission/order, stores, mutations, dispatch readiness/runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
33
+ - `runtime`: session-local coordination primitives: counters, lifecycle flags, setup guard, abort handler, typing-loop timers, prompt-dispatch flags, and agent-end reset binding.
34
+ - `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-queue` / `menu-settings` / `commands`: model identity/thinking levels, scoped model resolution, in-flight switching, model/thinking/status/queue/settings menu UI, inline application callback composition, slash commands, and bot command registration.
35
+ - Future `extension-sections`: structured external Telegram menu sections registered by ordinary pi extensions; owns section registry, compact section callback tokens, section render/callback dispatch, safe section runtime ports, and diagnostics.
36
+ - `keyboard`: shared Telegram inline-keyboard reply-markup structure; feature domains own callback semantics and button construction.
37
+ - `preview` / `replies` / `rendering`: preview lifecycle/transports, final reply delivery and reply parameters, Telegram HTML Markdown rendering, chunking, and stable-preview snapshots.
38
+ - `outbound-handlers`: outbound text transformation, assistant-authored outbound comments, generated reply artifacts, inline-keyboard callbacks, and post-`agent_end` outbound action delivery.
39
+ - `outbound-attachments`: `telegram_attach` registration, outbound attachment queueing, stat/limit checks, and photo/document delivery classification.
40
+ - `status`: status-bar/status-message rendering, queue-lane status views, redacted runtime event ring, and grouped π diagnostics.
41
+ - `lifecycle` / `prompts` / `prompt-templates` / `pi`: π hook registration, Telegram-specific before-agent prompt injection, π prompt-template discovery/expansion, and centralized direct pi SDK imports/context adapters.
42
+ - `command-templates`: portable shell-free command-template standard helpers, composition expansion, placeholder substitution, and executable resolution.
44
43
 
45
44
  Boundary invariants:
46
45
 
47
46
  - Constants and state types live with their owning domains; do not reintroduce shared buckets such as `lib/constants.ts` or `lib/types.ts`
48
- - Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`; core queue mechanics stay in `queue`
47
+ - Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`; future external section labels, callbacks, and dispatch stay in `extension-sections`; core queue mechanics stay in `queue`
49
48
  - Domain helpers use narrow structural projections when that avoids importing concrete wire DTOs or broader runtime objects unnecessarily
50
49
  - Preview appearance stays in `rendering`; preview transport/lifecycle stays in `preview`
51
50
  - Direct `node:*` file-operation imports stay in owning domains, not in `index.ts`
@@ -95,13 +94,11 @@ Queued items now use two explicit dimensions:
95
94
 
96
95
  Admission contract:
97
96
 
98
- | Admission | Examples | Queue shape | Dispatch rank |
99
- | --------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
100
- | Immediate execution | `/compact`, `/queue`, `/stop`, `/help`, `/start` | Does not enter the Telegram queue; `/help` opens the same menu as `/start`; `/stop` also clears queued items | N/A |
101
- | Queued prompt command | `/continue`, `/template_name args` | `/continue` enqueues a Telegram-owned `continue` prompt; prompt-template commands expand the matching π template before entering the normal prompt queue | priority for `/continue`, otherwise default |
102
- | Control queue | Model-switch continuation turns and future deferred controls | `queueLane: control`; accepts control items and continuation prompts | 0 |
103
- | Priority prompt queue | A waiting prompt promoted by `👍`, `⚡️`, `❤️`, `🕊`, or `🔥` | `kind: prompt`, `queueLane: priority` | 1 |
104
- | Default prompt queue | Normal Telegram text/media turns | `kind: prompt`, `queueLane: default` | 2 |
97
+ - Immediate execution: `/compact`, `/queue`, `/stop`, `/help`, and `/start` do not enter the Telegram queue. `/help` opens the same menu as `/start`; `/stop` also clears queued items. Dispatch rank: N/A.
98
+ - Queued prompt command: `/continue` enqueues a priority Telegram-owned `continue` prompt. Prompt-template commands such as `/template_name args` expand the matching π template before entering the normal prompt queue. Dispatch rank: priority for `/continue`, otherwise default.
99
+ - Control queue: model-switch continuation turns and future deferred controls use `queueLane: control`, accept control items and continuation prompts, and dispatch at rank `0`.
100
+ - Priority prompt queue: a waiting prompt promoted by `👍`, `⚡️`, `❤️`, `🕊`, or `🔥` uses `kind: prompt`, `queueLane: priority`, and dispatches at rank `1`.
101
+ - Default prompt queue: normal Telegram text/media turns use `kind: prompt`, `queueLane: default`, and dispatch at rank `2`.
105
102
 
106
103
  The command action itself carries its execution mode, and the queue domain exposes lane contracts for admission mode, dispatch rank, and allowed item kinds. Queue append and planning paths validate lane admission so a malformed control/default or other invalid lane pairing fails predictably instead of silently changing priority. This lets synthetic control actions and Telegram prompts share one stable ordering model while still rendering distinctly in status output. In the π status bar, busy labels distinguish `active`, `dispatching`, `queued`, `tool running`, `model`, and `compacting`; priority prompts and priority control items are marked with `⚡`. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, the status remains yellow `active` instead of degrading to green `connected`.
107
104
 
@@ -160,7 +157,7 @@ Telegram prompt responses use explicit delivery context to attach outbound text,
160
157
 
161
158
  Outbound files are sent only after the active Telegram turn completes, must be staged through the `telegram_attach` tool, are staged atomically per tool call, are checked against a default 50 MiB limit configurable through `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES`, and use file-backed multipart blobs so large sends do not require preloading whole files into memory.
162
159
 
163
- Assistant-authored outbound actions use final-message markup instead of agent tool calls. Preview updates strip closed top-level HTML comments and currently open/partial top-level comment starts before rendering, so users do not see transient metadata even when streaming flushes happen after only `<`, `<!`, or `<!--`. On `agent_end`, the bridge removes top-level comments from the Markdown text reply, but treats column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks specially before delivery; comments inside fenced code, quotes, lists, or indented examples stay literal, including fenced blocks with Markdown-valid indented closing fences. Voice maps to the first matching `outboundHandlers[]` entry with `type: "voice"`, synthesizes body text, `text="..."`, or colon shorthand through command-template execution, and uploads the generated OGG/Opus file via Telegram `sendVoice`; when no outbound voice handler is configured, it silently skips voice delivery. The `template: [...]` form can express TTS plus MP3-to-OGG conversion using configured templates and bridge-provided `{text}`, `{mp3}`, and `{ogg}` placeholders. Top-level `args` and `defaults` apply to all composed steps unless a step defines private values, the default command timeout applies automatically, and each step receives the previous step's stdout on stdin by default, without hard-coded filesystem defaults. Button blocks are built in: each `telegram_button` block becomes one inline-keyboard button on the final text, and callback clicks enqueue the configured prompt text as a normal Telegram prompt turn; the `telegram_button: Label` shorthand uses the same text for label and prompt, `prompt="..."` supports explicit one-line prompts, and body-form buttons use the body as the prompt. Unknown callback data that does not match pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`) is forwarded to π as `[callback] <data>` after built-in handlers decline it, giving layered extensions a simple namespaced button channel without separate polling; layered callback payloads should follow the [Callback Namespace Standard](./callback-namespaces.md). When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not sent because the bot does not own or mirror terminal user messages. This keeps terminal-originated results visible in Telegram without changing Telegram-originated turn delivery.
160
+ Assistant-authored outbound actions use final-message markup instead of agent tool calls. Preview updates strip closed top-level HTML comments and currently open/partial top-level comment starts before rendering, so users do not see transient metadata even when streaming flushes happen after only `<`, `<!`, or `<!--`. On `agent_end`, the bridge removes top-level comments from the Markdown text reply, but treats column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks specially before delivery; comments inside fenced code, quotes, lists, or indented examples stay literal, including fenced blocks with Markdown-valid indented closing fences. Voice maps to the first matching `outboundHandlers[]` entry with `type: "voice"`, synthesizes body text, `text="..."`, or colon shorthand through command-template execution, and uploads the generated OGG/Opus file via Telegram `sendVoice`; when no outbound voice handler is configured, it silently skips voice delivery. The `template: [...]` form can express TTS plus MP3-to-OGG conversion using configured templates and bridge-provided `{text}`, `{mp3}`, and `{ogg}` placeholders. Top-level `args` and `defaults` apply to all composed steps unless a step defines private values, the default command timeout applies automatically, and each step receives the previous step's stdout on stdin by default, without hard-coded filesystem defaults. Button blocks are built in: each `telegram_button` block becomes one inline-keyboard button on the final text, and callback clicks enqueue the configured prompt text as a normal Telegram prompt turn; the `telegram_button: Label` shorthand uses the same text for label and prompt, `prompt="..."` supports explicit one-line prompts, and body-form buttons use the body as the prompt. Unknown callback data that does not match pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, future `section:`) is forwarded to π as `[callback] <data>` after built-in handlers decline it, giving layered extensions a simple namespaced button channel without separate polling; layered callback payloads should follow the [Callback Namespace Standard](./callback-namespaces.md). Future structured menu integrations should use the [Telegram Extension Sections Standard](./extension-sections.md) instead of hand-rolled fallback callbacks. When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not sent because the bot does not own or mirror terminal user messages. This keeps terminal-originated results visible in Telegram without changing Telegram-originated turn delivery.
164
161
 
165
162
  This keeps technical Markdown, code, tables, formulas, and numbered lists in the text channel when appropriate while allowing TTS-friendly voice messages and tappable continuations without invoking `telegram_attach` or extra transport tools. Telegram prompt guidance targets about 37 visible cells for tables, dense list items, and compact text blocks because emoji and other wide glyphs make raw character counts misleading on mobile screens.
166
163
 
@@ -20,7 +20,7 @@ myext:page:2
20
20
 
21
21
  - Use a stable extension-owned namespace, preferably the package or extension name without scope punctuation.
22
22
  - Keep the namespace lowercase ASCII: `a-z`, `0-9`, `_`, `-`.
23
- - Do not use `pi-telegram` owned prefixes: `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI.
23
+ - Do not use `pi-telegram` owned prefixes: `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI. `section:` is reserved for the structured extension-section router documented in [Extension Sections](./extension-sections.md).
24
24
  - Keep the full `callback_data` within Telegram's 64-byte limit.
25
25
  - Put only opaque ids or small enum values in payloads; do not store secrets, full prompts, or large state.
26
26
  - Treat callbacks as untrusted input. Validate namespace, action, and payload before executing side effects.
@@ -34,3 +34,15 @@ If `pi-telegram` receives callback data that is not owned by its built-in prefix
34
34
  ```
35
35
 
36
36
  Layered extensions may intercept that message and handle their own namespace. If no extension handles it, the assistant may see the fallback message and should tell the user the callback was not handled and the environment may be misconfigured.
37
+
38
+ ## Extension sections
39
+
40
+ [Telegram Extension Sections](./extension-sections.md) are a higher-level UI contract over this namespace rule. A section owns a canonical extension identity such as `@llblab/pi-telegram-explorer`, but its Telegram `callback_data` should use the `pi-telegram` owned `section:` prefix plus a compact token, because Telegram limits callback payloads to 64 bytes.
41
+
42
+ Conceptual form:
43
+
44
+ ```text
45
+ section:<token>:<action>[:<payload>]
46
+ ```
47
+
48
+ The token maps back to the full section identity inside the section registry. Section authors should not hand-roll `section:` callbacks outside the section context helpers, and ordinary layered extensions should continue using their own namespace plus external handlers or the `[callback]` fallback.