@llblab/pi-telegram 0.18.6 → 0.19.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 +2 -2
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +8 -1
- package/README.md +176 -226
- package/docs/architecture.md +3 -3
- package/index.ts +1 -0
- package/lib/config.ts +22 -0
- package/lib/menu-settings.ts +88 -0
- package/lib/updates.ts +1 -1
- package/package.json +2 -2
- /package/{screenshot.png → banner.png} +0 -0
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
37
37
|
- `/.agents/skills/`: Project-local agent skills that support this repository. Keep each skill's durable operating guidance in its own `SKILL.md`; use `AGENTS.md` only for topology and maintenance rules.
|
|
38
38
|
- `telegram-bot`: Agent-facing Telegram Bot API lookup skill. Keep the vendored `api.md` reference intact and put navigation, freshness, line-range, task, risk, and synonym indexes in `SKILL.md`.
|
|
39
39
|
- `domain-dag`: Agent-facing architecture skill and validator for this repository's flat Domain DAG, composition-root, header, cycle, and shared-bucket invariants.
|
|
40
|
-
- `/README.md`: User-facing project entry point and product hyperindex. It is the public representation of the extension, not agent-maintenance context. Keep its rhythm as identity →
|
|
40
|
+
- `/README.md`: User-facing project entry point and product hyperindex. It is the public representation of the extension, not agent-maintenance context. Keep its rhythm as identity → install/connect → lived examples → product model → compact feature showcase → controls/surfaces → safety boundaries → docs. Preserve both layers: strong product positioning plus a practical feature catalogue. Do not let the README collapse into abstract positioning that hides capabilities, and do not let it regress into an implementation dump that duplicates full docs. It should describe the extension through several durable lenses — operator companion, Telegram UI harness, Pi runtime adapter, multi-instance/thread organism, companion-extension platform, delivery/media surface, safety boundary, and release maturity — so a reader can understand practical value without wading into minor implementation detail.
|
|
41
41
|
- `/AGENTS.md`: Durable engineering and runtime conventions
|
|
42
42
|
- `/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.
|
|
43
43
|
- `/CHANGELOG.md`: Completed delivery history focused on the final released behavior and user/operator/developer impact. Prefer multiple domain-scoped bullets in the form `[Domain]`: change + impact instead of accumulating unrelated changes into one long entry. Do not record transient implementation churn such as "added then removed" mechanics, internal reversions, or cleanup of an abandoned intermediate path unless the final product surface exposes that as a meaningful migration/breaking change.
|
|
@@ -103,7 +103,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
103
103
|
|
|
104
104
|
## 5.3 Telegram Delivery Semantics
|
|
105
105
|
|
|
106
|
-
-
|
|
106
|
+
- Rich Markdown is the model-answer membrane: use Telegram-native Rich Message APIs for complete assistant/guest model replies only, not for tool-call rows, reasoning/thinking blocks, menus, status rows, queue controls, settings, diagnostics, or other harness-owned technical surfaces. Those bridge-owned surfaces should keep explicit Telegram HTML/plain rendering by default because readability, operational predictability, and surface ownership are higher there. Companion sections may explicitly choose Markdown, HTML, or plain text per view. Keep native Rich Markdown source close to model-authored Markdown, but normalize Bot-API-fragile equivalents when evidence shows a Telegram parser/client edge, such as space-after-marker blockquotes (`> quote` -> `>quote`) and dollar-prefixed ticker atoms (`$BLDR` -> `\$BLDR`) outside code fences/spans
|
|
107
107
|
- Use `.agents/skills/telegram-bot/SKILL.md` and its `api.md` reference for native Rich Markdown, Bot API topic transport, and transport capability checks
|
|
108
108
|
- Formula guidance belongs in the Telegram-turn prompt contract: use `$...$` for inline math and `$$...$$` for block math; backticks intentionally render formulas as literal code
|
|
109
109
|
- Real code blocks must stay literal and escaped
|
package/BACKLOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Project Backlog
|
|
2
2
|
|
|
3
|
-
_Current deterministic status: Threaded Mode implementation, native typing/activity status, regression coverage, docs/context reconciliation, typecheck, full tests, pack check, audit, Domain DAG validation, context validation, native Windows classic↔Threaded Mode upgrade/downgrade smoke, and live post-reload leader/follower prompt routing, follower Active parity, and unbound reroute/restore smoke are green. This backlog intentionally tracks only release-relevant remaining work: evidence-gated Telegram client/runtime follow-ups and upstream Pi API blockers._
|
|
3
|
+
_Current deterministic status: Threaded Mode implementation, native typing/activity status, regression coverage, docs/context reconciliation, typecheck, full tests, pack check, audit, Domain DAG validation, context validation, native Windows classic↔Threaded Mode upgrade/downgrade smoke, and live post-reload leader/follower prompt routing, follower Active parity, and unbound reroute/restore smoke are green. This backlog intentionally tracks only release-relevant remaining work: 0.19.0 release preparation, evidence-gated Telegram client/runtime follow-ups, and upstream Pi API blockers._
|
|
4
4
|
|
|
5
5
|
## P0 — Promoted Follower Reload Smoke
|
|
6
6
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.19.0: Telegram Companion Hub
|
|
4
|
+
|
|
5
|
+
- `[Context]` Defined Rich Markdown as the model-answer membrane: complete assistant/guest model replies use native Rich Message delivery, while tool rows, reasoning/thinking blocks, menus, status, queue controls, settings, diagnostics, and other harness-owned surfaces stay on explicit Telegram HTML/plain rendering.
|
|
6
|
+
- `[Settings]` Added an opt-in `richDraftPreviews` setting, exposed it in Telegram Settings, and gated `sendRichMessageDraft` preview frames behind it while preserving final native Rich Markdown replies. Impact: fresh installs default to final-only Rich Markdown plus native active status, and operators can explicitly enable progressive draft drawing when desired.
|
|
7
|
+
- `[Backlog]` Opened 0.19.0 release preparation and closed the rich draft preview setting task, making final-only Rich Markdown replies the intended baseline and draft previews an opt-in progressive enhancement.
|
|
8
|
+
- `[Context]` Clarified the README standard: keep the root entrypoint balanced between product positioning and a compact practical feature showcase, avoiding both over-abstract marketing copy and duplicated implementation docs. Impact: future README changes preserve the feature-catalogue value while keeping the surface coherent.
|
|
9
|
+
- `[Docs]` Reworked the root README as a product-oriented RhythmE entrypoint with clearer hero positioning, install/connect flow, operating model, expanded feature showcase, classic-vs-Threaded Mode comparison, safety boundaries, extension platform summary, and documentation map. Impact: the public entrypoint now explains `pi-telegram` as a Telegram companion console while preserving a practical catalogue of user-facing capabilities.
|
|
10
|
+
- `[Guest Mode]` Unauthorized guest-query replies now include the standard denied-action emoji. Impact: the compact `Access denied` message is easier to recognize in Telegram.
|
|
4
11
|
|
|
5
12
|
## 0.18.6: Threaded Mode parity hotfix
|
|
6
13
|
|
package/README.md
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
# pi-telegram
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
|
-
**Telegram
|
|
5
|
+
**A Telegram companion hub for live Pi sessions.**
|
|
6
6
|
|
|
7
|
-
`pi-telegram` turns a private Telegram DM into a
|
|
7
|
+
`pi-telegram` turns a private Telegram DM into a mobile operator surface for Pi. It accepts prompts, queues work, streams readable previews, delivers final replies and files, exposes safe controls, and lets companion extensions add Telegram-native capabilities without owning a second bot loop.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
It is a **runtime adapter**, not a remote terminal. Start or supervise work in the Pi TUI, then continue from Telegram while away from the keyboard. The bridge preserves Pi session semantics instead of pretending Telegram is a PTY, shell, or process launcher. That boundary is the product: Telegram gets safe runtime handles, not raw terminal power.
|
|
10
10
|
|
|
11
11
|
This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
|
|
12
12
|
|
|
13
|
-
## What this gives you
|
|
14
|
-
|
|
15
|
-
- **Mobile supervision**: continue a live Pi session from Telegram without turning Telegram into a fake terminal.
|
|
16
|
-
- **Telegram-native controls**: menus, settings, queue controls, native active status, Rich Markdown replies, drafts, buttons, voice, files, and artifacts.
|
|
17
|
-
- **Safe runtime mapping**: Telegram turns map into Pi lifecycle, queueing, model switching, compaction, previews, final replies, and ownership rules.
|
|
18
|
-
- **Optional Threaded Mode**: one leader and visible follower Pi processes can share one bot through named Telegram threads.
|
|
19
|
-
- **Extension platform**: companion extensions can add Telegram-native commands, sections, status rows, update handlers, handlers, and voice providers without owning polling.
|
|
20
|
-
|
|
21
|
-
Use this README for the product shape. Follow the docs for exact contracts.
|
|
22
|
-
|
|
23
13
|
## Install
|
|
24
14
|
|
|
25
15
|
From npm:
|
|
@@ -34,24 +24,24 @@ From git:
|
|
|
34
24
|
pi install git:github.com/llblab/pi-telegram
|
|
35
25
|
```
|
|
36
26
|
|
|
37
|
-
##
|
|
27
|
+
## Quick Start
|
|
38
28
|
|
|
39
29
|
### 1. Create a Telegram bot
|
|
40
30
|
|
|
41
|
-
1. Open [@BotFather](https://t.me/BotFather)
|
|
42
|
-
2. Run `/newbot
|
|
43
|
-
3. Pick a name and username
|
|
44
|
-
4. Copy the bot token
|
|
31
|
+
1. Open [@BotFather](https://t.me/BotFather).
|
|
32
|
+
2. Run `/newbot`.
|
|
33
|
+
3. Pick a name and username.
|
|
34
|
+
4. Copy the bot token.
|
|
45
35
|
|
|
46
|
-
### 2. Configure
|
|
36
|
+
### 2. Configure Pi
|
|
47
37
|
|
|
48
|
-
|
|
38
|
+
Run this inside Pi:
|
|
49
39
|
|
|
50
40
|
```bash
|
|
51
41
|
/telegram-setup
|
|
52
42
|
```
|
|
53
43
|
|
|
54
|
-
Paste
|
|
44
|
+
Paste the bot token. If `~/.pi/agent/telegram.json` already contains a saved token, setup offers it as the default. If no saved token exists, setup can prefill from `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_KEY`, `TELEGRAM_TOKEN`, or `TELEGRAM_KEY`.
|
|
55
45
|
|
|
56
46
|
### 3. Connect this Pi session
|
|
57
47
|
|
|
@@ -59,262 +49,222 @@ Paste your bot token when prompted. If a bot token is already saved in `~/.pi/ag
|
|
|
59
49
|
/telegram-connect
|
|
60
50
|
```
|
|
61
51
|
|
|
62
|
-
The
|
|
52
|
+
The connected Pi instance owns Telegram polling. In classic mode this is a singleton lock. When Telegram private-chat Threaded Mode is available, one live instance becomes the leader and later visible Pi instances register as followers.
|
|
63
53
|
|
|
64
54
|
### 4. Pair your Telegram account
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
2. Send `/start`
|
|
68
|
-
|
|
69
|
-
The first user to message the bot becomes the exclusive owner of the adapter. Messages from other users are ignored.
|
|
70
|
-
|
|
71
|
-
### Environment-only configuration
|
|
72
|
-
|
|
73
|
-
Most day-to-day controls live in the Telegram menu or Pi commands. A few important runtime knobs intentionally stay in environment variables because they affect bootstrap, networking, or transport limits before a menu can help:
|
|
74
|
-
|
|
75
|
-
- **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.
|
|
76
|
-
- **HTTP/HTTPS proxy**: native `fetch` can use `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` when Node's environment proxy mode is enabled. Use `NODE_USE_ENV_PROXY=1` or start Node with `--use-env-proxy`. SOCKS5 is not part of the zero-dependency core. If you need it, run a local HTTP-to-SOCKS bridge or system tunnel and point `HTTP_PROXY` / `HTTPS_PROXY` at the HTTP endpoint.
|
|
77
|
-
- **Telegram network family**: `PI_TELEGRAM_NETWORK_FAMILY=auto|ipv4|ipv6|ipv4-fallback` controls Bot API transport only. The default is `ipv4-fallback`: try native `fetch` first, then retry transport-level failures through IPv4-only HTTPS. Use `auto` to force native `fetch` only, or `ipv4`/`ipv6` to force a family.
|
|
78
|
-
- **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`.
|
|
79
|
-
- **Inbound file limit**: `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES` changes the default 50 MiB Telegram download limit.
|
|
80
|
-
- **Outbound attachment limit**: `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` changes the default 50 MiB `telegram_attach` delivery limit.
|
|
81
|
-
|
|
82
|
-
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.
|
|
83
|
-
|
|
84
|
-
Long-running Telegram turns use Telegram's native active status as the activity indicator: technically Bot API `sendChatAction(typing)`, presented in product language as `...active` even when Telegram clients render it as typing dots. Active status is the only automatic in-chat work signal before the final reply.
|
|
85
|
-
|
|
86
|
-
Set these variables before launching Pi. 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.
|
|
87
|
-
|
|
88
|
-
## Use
|
|
89
|
-
|
|
90
|
-
Once paired, chat with your bot in Telegram. Text, images, files, replies, edits, media groups, and configured handler output are forwarded into Pi as Telegram-originated turns.
|
|
91
|
-
|
|
92
|
-
What it feels like:
|
|
93
|
-
|
|
94
|
-
- Start work in the terminal, walk away, and keep supervising the same live Pi session from Telegram.
|
|
95
|
-
- Open `/start` and get a Telegram control panel for the running Pi session: status, prompt templates, model, thinking, settings, and queue.
|
|
96
|
-
- Fire off three tasks while Pi is busy. They become visible queue items instead of terminal noise.
|
|
97
|
-
- Open Queue from the menu, inspect waiting work, delete stale prompts, or move important work forward.
|
|
98
|
-
- Switch models from Telegram mid-run; the adapter schedules a safe continuation instead of tearing state apart.
|
|
99
|
-
- Send a voice note; a configured inbound handler or registered STT provider transcribes it; Pi answers in the same chat.
|
|
100
|
-
- Drop a screenshot and ask, "what is broken here?" The image payload reaches Pi with the local file context.
|
|
101
|
-
- Ask for a generated file; when Pi calls `telegram_attach`, the artifact returns with the active Telegram reply or is sent directly to the paired/default chat from local work.
|
|
102
|
-
|
|
103
|
-
### Telegram controls
|
|
104
|
-
|
|
105
|
-
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 can be safely adapted through Pi's extension APIs. The bot does not forward arbitrary terminal slash commands or emulate TUI-only session controls.
|
|
106
|
-
|
|
107
|
-
- **`/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.
|
|
108
|
-
- **`/compact`**: Ask for inline confirmation, then start session compaction when the session is idle; Telegram shows the native active indicator while manual or automatic compaction is running.
|
|
109
|
-
- **`/next`**: Dispatch the next queued turn, aborting Pi first if needed.
|
|
110
|
-
- **`/continue`**: Enqueue a priority `continue` prompt.
|
|
111
|
-
- **`/abort`**: Abort the active run without touching the queue. Abort-history applies only to Telegram-owned active turns; later local prompts do not make the next Telegram prompt absorb older queue items.
|
|
112
|
-
- **`/stop`**: Abort the active run and clear waiting Telegram queue items.
|
|
113
|
-
|
|
114
|
-
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.
|
|
115
|
-
|
|
116
|
-
Prompt-template commands are discovered from Pi prompt templates, mapped to Telegram-safe aliases (`fix-tests.md` becomes `/fix_tests`), shown in `/start`, and expanded before queueing.
|
|
117
|
-
|
|
118
|
-
### Pi commands
|
|
119
|
-
|
|
120
|
-
Run these inside Pi, not Telegram:
|
|
121
|
-
|
|
122
|
-
- **`/telegram-setup`**: Configure or update the Telegram bot token.
|
|
123
|
-
- **`/telegram-connect`**: Start polling Telegram updates in the current Pi session and acquire the singleton lock.
|
|
124
|
-
- **`/telegram-disconnect`**: Stop polling in the current Pi session and release the singleton lock.
|
|
125
|
-
- **`/telegram-status`**: Inspect adapter status, connection, polling, execution, queue, and recent redacted runtime/API failure events.
|
|
126
|
-
|
|
127
|
-
### Files and artifacts
|
|
56
|
+
Open the bot DM and send:
|
|
128
57
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
### Telegram Threaded Mode and multi-instance bus
|
|
134
|
-
|
|
135
|
-
Telegram private-chat Threaded Mode is the switch. Classic single-DM polling is the base mode. When Telegram reports private-chat threads are available for the bot, the adapter enables the local leader/follower bus automatically; when threads are unavailable or later disabled, it uses classic single-DM polling as the ordinary private-bot mode.
|
|
136
|
-
|
|
137
|
-
Only the leader calls `getUpdates`; followers authenticate to the local bus and route allowlisted, target-scoped Telegram work through the leader. When private-chat threads are available, they become the UI targets:
|
|
138
|
-
|
|
139
|
-
- The leader owns one thread;
|
|
140
|
-
- Each explicitly connected follower gets one visible thread;
|
|
141
|
-
- Telegram never launches hidden Pi follower processes;
|
|
142
|
-
- New thread names are assigned by the bridge from a compact curated palette, while existing human names are preserved;
|
|
143
|
-
- Unknown owner-created threads preserve the original prompt and offer a target-thread chooser instead of spawning work invisibly;
|
|
144
|
-
- Stale follower tabs receive compact lifecycle notices before cleanup when the leader can prove ownership.
|
|
145
|
-
|
|
146
|
-
Thread input is still authorized by `allowedUserId`. There is no separate public `telegram.json` switch for the bus: Telegram capability detection is the runtime source of truth. Native Windows Threaded Mode smoke remains tracked in `BACKLOG.md`; the intended transport is the same local bus over Windows named pipes instead of Unix sockets.
|
|
147
|
-
|
|
148
|
-
## Core features
|
|
149
|
-
|
|
150
|
-
### Operator menu and controls
|
|
151
|
-
|
|
152
|
-
The inline application menu is the primary operator surface. It exposes status, prompt-template commands, companion-extension Telegram 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 Pi session running without touching the terminal.
|
|
153
|
-
|
|
154
|
-
### Queue runtime
|
|
155
|
-
|
|
156
|
-
Messages sent while Pi is busy enter the prompt queue and are processed in order. Control actions and model-switch continuation turns use higher-priority lanes. Queue processing and reply delivery stay local to the Pi instance that accepted the work, even if `/telegram-connect` later moves elsewhere.
|
|
157
|
-
|
|
158
|
-
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. The same rules apply to text, voice, files, images, and media groups:
|
|
58
|
+
```text
|
|
59
|
+
/start
|
|
60
|
+
```
|
|
159
61
|
|
|
160
|
-
|
|
161
|
-
|
|
62
|
+
The first Telegram user to message the bot becomes the allowed owner. Other users are ignored.
|
|
63
|
+
|
|
64
|
+
## What It Feels Like
|
|
65
|
+
|
|
66
|
+
- Start a task in the terminal, walk away, and keep supervising it from your phone.
|
|
67
|
+
- Send another prompt while Pi is busy; it becomes a queued Telegram turn instead of interrupting the active run.
|
|
68
|
+
- Open `/start` to inspect status, model, thinking, settings, prompt templates, and queue controls.
|
|
69
|
+
- Send voice, images, files, replies, edits, or media groups; the bridge turns them into Pi context.
|
|
70
|
+
- Ask for an artifact; `telegram_attach` returns it through the active reply or direct Telegram delivery.
|
|
71
|
+
- In Threaded Mode, run multiple visible Pi instances through one bot, each with its own Telegram thread.
|
|
72
|
+
|
|
73
|
+
## Product Model
|
|
74
|
+
|
|
75
|
+
| Lens | What `pi-telegram` owns |
|
|
76
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------- |
|
|
77
|
+
| Operator companion | A phone-width control surface for a live Pi session |
|
|
78
|
+
| Runtime adapter | Telegram turns mapped into Pi lifecycle, queueing, previews, final replies, and artifacts |
|
|
79
|
+
| Telegram UI harness | Menus, settings, callbacks, Rich Markdown, drafts, active status, buttons, voice, and files |
|
|
80
|
+
| Multi-instance organism | One leader plus explicit visible followers routed through Telegram private-chat threads |
|
|
81
|
+
| Extension platform | Commands, sections, status rows, update handlers, inbound/outbound handlers, and voice providers |
|
|
82
|
+
| Safety boundary | No hidden Pi processes, no fake terminal, no PTY tricks, no arbitrary TUI slash-command forwarding |
|
|
83
|
+
|
|
84
|
+
## Feature Showcase
|
|
85
|
+
|
|
86
|
+
`pi-telegram` is intentionally broad: it is a Telegram-shaped runtime surface, not only a message relay. This catalogue keeps the practical feature surface visible while detailed contracts stay in `/docs`.
|
|
87
|
+
|
|
88
|
+
| Surface | What you can do | Why it matters |
|
|
89
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| Prompt intake | Send text, replies, edits, images, files, albums, voice notes, and handler output into Pi. | Telegram becomes a real mobile input surface with file/context references, not just a text tunnel. |
|
|
91
|
+
| Queue control | Inspect waiting turns, delete stale work, promote important prompts, continue, abort, stop, or force the next queued item. | Long Pi tasks keep running while new mobile prompts stay visible and controllable instead of interrupting or disappearing. |
|
|
92
|
+
| Operator menu | Use `/start` for status, prompt templates, model, thinking, settings, queue, extension sections, and diagnostics. | The bot is an operator panel, not a command cheat sheet. |
|
|
93
|
+
| Prompt templates | Run Pi prompt templates as Telegram-safe commands such as `/fix_tests`. | Reusable local workflows become phone-accessible without exposing arbitrary terminal commands. |
|
|
94
|
+
| Model and thinking | Switch model or reasoning level from Telegram through safe continuation flows. | Mobile control can adjust execution strategy without tearing down the current session. |
|
|
95
|
+
| Compaction | Confirm `/compact`, show native active status during compaction, and preserve Telegram-owned turn semantics. | Context maintenance is visible and safe from the phone. |
|
|
96
|
+
| Streaming previews | Show native active status and, when enabled, Rich Markdown drafts while the agent is still working. | The operator can see progress without making draft rendering the baseline. |
|
|
97
|
+
| Final Rich Markdown | Deliver complete assistant/guest model replies through Telegram Rich Message APIs with safe splitting. | Meaningful model answers stay readable on narrow mobile screens without forcing generic HTML conversion. |
|
|
98
|
+
| Bridge UI rendering | Render tool rows, reasoning/technical steps, menus, queue controls, status, settings, diagnostics, and sections through explicit Telegram HTML/plain UI. | Harness-owned surfaces remain operationally predictable and visually distinct from model-authored answers. |
|
|
99
|
+
| Inbound files | Download inbound files to the Pi agent temp directory with size limits. | Screenshots, PDFs, datasets, and artifacts enter Pi as inspectable local files. |
|
|
100
|
+
| Outbound artifacts | Return generated files through `telegram_attach` during active turns or explicit direct delivery. | Agents send real artifacts as files, not pasted blobs. |
|
|
101
|
+
| Voice input | Route audio through configured command-template handlers, programmatic handlers, or STT providers. | Voice notes become usable prompt context. |
|
|
102
|
+
| Voice output | Use `telegram_voice`, reply modes, configured voice handlers, and TTS providers. | Replies can become Telegram voice messages when the workflow calls for it. |
|
|
103
|
+
| Buttons | Turn top-level `telegram_button` comments into inline buttons. | Assistant-authored choices become native Telegram interactions. |
|
|
104
|
+
| Callback routing | Route known callbacks to the owner extension and unknown callbacks back into Pi. | Companion extensions can build UI without polling Telegram themselves. |
|
|
105
|
+
| Threaded Mode | Run one leader plus visible follower Pi instances through named private-chat threads. | One bot can host a local multi-instance Pi organism without hidden process spawning. |
|
|
106
|
+
| Reroute and restore | Preserve unknown threads and offer explicit target choices. | Telegram client state can be repaired without silently deleting or hijacking prompts. |
|
|
107
|
+
| Extension sections | Add menu sections, commands, status rows, settings, callbacks, and delivery helpers from companion extensions. | `pi-telegram` becomes a platform surface for other Pi extensions. |
|
|
108
|
+
| Runtime diagnostics | Use `/telegram-status` and recent runtime events for connection, role, queue, transport, and failure evidence. | Debugging lives in the operator surface instead of hidden logs only. |
|
|
109
|
+
| Safety and ownership | Pair one owner, lock transport, scope targets, and reject fake terminal behavior. | Remote access remains explicit, bounded, and understandable. |
|
|
110
|
+
|
|
111
|
+
## Core Loop
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
Telegram message
|
|
115
|
+
-> Telegram turn
|
|
116
|
+
-> queue or active dispatch
|
|
117
|
+
-> Pi agent lifecycle
|
|
118
|
+
-> streaming preview / native active status
|
|
119
|
+
-> final Rich Markdown reply
|
|
120
|
+
-> optional files, voice, buttons, or callback actions
|
|
121
|
+
```
|
|
162
122
|
|
|
163
|
-
|
|
123
|
+
The bridge keeps Telegram responsive without stealing Pi's runtime model. Queueing, model changes, compaction, aborts, final delivery, and direct artifact sends all stay scoped to the Pi instance that accepted the work.
|
|
164
124
|
|
|
165
|
-
|
|
125
|
+
## Telegram Controls
|
|
166
126
|
|
|
167
|
-
|
|
127
|
+
Use these in the bot DM.
|
|
168
128
|
|
|
169
|
-
|
|
129
|
+
| Command | Purpose |
|
|
130
|
+
| ----------- | ------------------------------------------------------- |
|
|
131
|
+
| `/start` | Pair when needed and open the main operator menu |
|
|
132
|
+
| `/compact` | Confirm and run session compaction when safe |
|
|
133
|
+
| `/next` | Dispatch the next queued turn, aborting first if needed |
|
|
134
|
+
| `/continue` | Enqueue a priority continuation prompt |
|
|
135
|
+
| `/abort` | Abort the active run while preserving the queue |
|
|
136
|
+
| `/stop` | Abort the active run and clear waiting Telegram turns |
|
|
170
137
|
|
|
171
|
-
|
|
138
|
+
Hidden compatibility shortcuts: `/help`, `/status`, `/model`, `/thinking`, `/queue`, and `/settings` jump into the same menu system.
|
|
172
139
|
|
|
173
|
-
|
|
140
|
+
## Pi Commands
|
|
174
141
|
|
|
175
|
-
|
|
142
|
+
Run these inside Pi.
|
|
176
143
|
|
|
177
|
-
|
|
144
|
+
| Command | Purpose |
|
|
145
|
+
| ---------------------- | ------------------------------------------------------------------ |
|
|
146
|
+
| `/telegram-setup` | Save or update the bot token |
|
|
147
|
+
| `/telegram-connect` | Start polling and acquire Telegram transport ownership |
|
|
148
|
+
| `/telegram-disconnect` | Stop polling and release ownership |
|
|
149
|
+
| `/telegram-status` | Inspect connection, mode, queue, transport, and recent diagnostics |
|
|
178
150
|
|
|
179
|
-
|
|
180
|
-
{
|
|
181
|
-
"inboundHandlers": [
|
|
182
|
-
{
|
|
183
|
-
"type": "text",
|
|
184
|
-
"template": "/path/to/translate --lang {lang=en} --text \"{text}\""
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
"type": "voice",
|
|
188
|
-
"template": [
|
|
189
|
-
"/path/to/stt --file {file} --lang {lang=ru}",
|
|
190
|
-
"/path/to/translate-stdin --lang {lang=en}"
|
|
191
|
-
]
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
"mime": "audio/*",
|
|
195
|
-
"template": [
|
|
196
|
-
"/path/to/stt-fallback --file {file} --lang {lang=ru}",
|
|
197
|
-
"/path/to/translate-stdin --lang {lang=en}"
|
|
198
|
-
]
|
|
199
|
-
}
|
|
200
|
-
]
|
|
201
|
-
}
|
|
202
|
-
```
|
|
151
|
+
## Main Surfaces
|
|
203
152
|
|
|
204
|
-
###
|
|
153
|
+
### Operator Menu
|
|
205
154
|
|
|
206
|
-
|
|
155
|
+
`/start` opens the Telegram-native control panel: status, prompt-template commands, model selection, thinking level, settings, queue controls, and extension sections. It is the primary Telegram UI; reaction shortcuts are secondary queue affordances.
|
|
207
156
|
|
|
208
|
-
|
|
157
|
+
### Queue Runtime
|
|
209
158
|
|
|
210
|
-
|
|
211
|
-
Full technical answer stays readable as text.
|
|
159
|
+
Messages sent while Pi is busy become queued turns. Priority lanes support control actions and model-switch continuations. Queue controls let you inspect, delete, promote, and dispatch work from Telegram without touching the terminal.
|
|
212
160
|
|
|
213
|
-
|
|
214
|
-
Text to synthesize as a Telegram voice message.
|
|
215
|
-
-->
|
|
161
|
+
### Native Rich Markdown
|
|
216
162
|
|
|
217
|
-
|
|
218
|
-
List the main risks first.
|
|
219
|
-
-->
|
|
220
|
-
```
|
|
163
|
+
Rich Markdown is the model-answer membrane. Complete assistant and guest model replies use Telegram's native Rich Message APIs, while tool-call rows, reasoning/thinking blocks, menus, status rows, queue controls, settings, diagnostics, and other harness-owned surfaces use explicit Telegram HTML/plain rendering. This keeps meaningful model-authored answers visually distinct from bridge-owned operational UI. Streaming previews may use rich-message drafts when enabled; final replies persist through `sendRichMessage`.
|
|
221
164
|
|
|
222
|
-
|
|
165
|
+
### Files And Artifacts
|
|
223
166
|
|
|
224
|
-
|
|
167
|
+
Inbound files land under `<agent-dir>/tmp/telegram` and default to a 50 MiB limit. `telegram_attach` is the canonical outbound file path. During Telegram-originated turns it attaches to the active reply; during explicit local/TUI delivery it can send to the paired/default chat or routed Threaded Mode target.
|
|
225
168
|
|
|
226
|
-
### Voice
|
|
169
|
+
### Voice And Media
|
|
227
170
|
|
|
228
|
-
|
|
171
|
+
Voice notes, audio, images, PDFs, and other media can pass through configured inbound handlers, programmatic handlers, or registered STT providers. Outbound voice can use configured `outboundHandlers` or registered TTS providers; `pi-telegram` owns reply policy and Telegram transport, while providers own synthesis.
|
|
229
172
|
|
|
230
|
-
|
|
231
|
-
- `manual`: agent-authored `<!-- telegram_voice -->` markup is required for voice replies; no automatic conversion. Unlike `hidden`, this explicit mode adds `[voice] reply mode: manual` context.
|
|
232
|
-
- `mirror`: when the user sends a voice message, the next reply is converted to voice and text preview is suppressed. Text-originated turns stay on the normal/manual text path, so agent-authored `<!-- telegram_voice -->` markup still works explicitly.
|
|
233
|
-
- `always`: every reply is converted to voice and text preview is suppressed.
|
|
173
|
+
### Buttons And Callbacks
|
|
234
174
|
|
|
235
|
-
|
|
175
|
+
Assistant replies can include top-level hidden `telegram_button` comments. The bridge strips the comments from visible text, renders inline buttons, and routes callbacks back into Pi as queued prompts or extension-owned callback actions.
|
|
236
176
|
|
|
237
|
-
|
|
177
|
+
### Threaded Mode And Multi-Instance Bus
|
|
238
178
|
|
|
239
|
-
|
|
179
|
+
Classic private DM mode is the base product mode. When Telegram private-chat Threaded Mode is available, the bridge enables a local leader/follower bus automatically:
|
|
240
180
|
|
|
241
|
-
|
|
181
|
+
- One live leader owns `getUpdates`.
|
|
182
|
+
- Followers are visible Pi processes started by the operator.
|
|
183
|
+
- Each connected instance gets a Telegram thread target.
|
|
184
|
+
- Unknown threads are preserved and offered explicit reroute/restore choices.
|
|
185
|
+
- Telegram never launches hidden Pi processes.
|
|
242
186
|
|
|
243
|
-
|
|
187
|
+
| Mode | Best for | Runtime shape |
|
|
188
|
+
| ------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
189
|
+
| Classic DM | One live Pi session controlled from one private bot chat | One polling owner, one queue/runtime surface |
|
|
190
|
+
| Threaded Mode | Several visible Pi terminals sharing one bot | One leader owns transport; followers route through named private-chat threads |
|
|
244
191
|
|
|
245
|
-
|
|
192
|
+
## Environment Configuration
|
|
246
193
|
|
|
247
|
-
|
|
194
|
+
Most controls live in Pi commands or the Telegram menu. Environment variables remain for bootstrap and transport boundaries:
|
|
248
195
|
|
|
249
|
-
|
|
196
|
+
| Area | Variables |
|
|
197
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
198
|
+
| Bot token bootstrap | `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_KEY`, `TELEGRAM_TOKEN`, `TELEGRAM_KEY` |
|
|
199
|
+
| HTTP proxy | `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, plus `NODE_USE_ENV_PROXY=1` or Node `--use-env-proxy` |
|
|
200
|
+
| Telegram network family | `PI_TELEGRAM_NETWORK_FAMILY`=auto | ipv4 | ipv6 | ipv4-fallback` |
|
|
201
|
+
| Agent data root | `PI_CODING_AGENT_DIR` |
|
|
202
|
+
| Inbound file limit | `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES`, `TELEGRAM_MAX_FILE_SIZE_BYTES` |
|
|
203
|
+
| Outbound attachment limit | `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES`, `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` |
|
|
250
204
|
|
|
251
|
-
|
|
205
|
+
Defaults are chosen for ordinary private-bot use: saved config in `~/.pi/agent`, inbound temp files in `~/.pi/agent/tmp/telegram`, native Rich Markdown replies, and native Telegram active status for long-running turns.
|
|
252
206
|
|
|
253
|
-
|
|
207
|
+
## Extension Platform
|
|
254
208
|
|
|
255
|
-
|
|
209
|
+
Companion extensions can integrate with Telegram without owning polling or transport:
|
|
256
210
|
|
|
257
|
-
|
|
211
|
+
- Register Telegram slash commands.
|
|
212
|
+
- Add menu sections and settings surfaces.
|
|
213
|
+
- Add compact status rows.
|
|
214
|
+
- Handle update/callback namespaces.
|
|
215
|
+
- Provide inbound preprocessing handlers.
|
|
216
|
+
- Provide outbound voice synthesis.
|
|
217
|
+
- Use direct delivery helpers for explicit local/TUI sends.
|
|
258
218
|
|
|
259
|
-
|
|
219
|
+
Stable public entrypoints are documented in [Public API](./docs/public-api.md), [Extension Sections](./docs/sections.md), [Inbound Handlers](./docs/inbound.md), [Outbound Handlers](./docs/outbound.md), [Updates](./docs/updates.md), and [Voice Integration](./docs/voice.md).
|
|
260
220
|
|
|
261
|
-
|
|
262
|
-
{
|
|
263
|
-
"time": {
|
|
264
|
-
"injectionMode": "interval",
|
|
265
|
-
"interval": 3600000
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
```
|
|
221
|
+
## Safety Boundaries
|
|
269
222
|
|
|
270
|
-
|
|
223
|
+
`pi-telegram` intentionally does not:
|
|
271
224
|
|
|
272
|
-
|
|
225
|
+
- Spawn hidden Pi follower processes.
|
|
226
|
+
- Pretend Telegram is a terminal or PTY.
|
|
227
|
+
- Forward arbitrary Telegram slash commands into the Pi TUI.
|
|
228
|
+
- Inject raw TTY input or terminal-control sequences.
|
|
229
|
+
- Replace Pi session lifecycle without an official Pi API.
|
|
230
|
+
- Let non-owner Telegram users control the bridge.
|
|
273
231
|
|
|
274
|
-
|
|
275
|
-
- [Open Backlog](./BACKLOG.md): planned work and known follow-ups.
|
|
276
|
-
- [Changelog](./CHANGELOG.md): completed delivery history.
|
|
277
|
-
- [Documentation Index](./docs/README.md): technical docs hub.
|
|
278
|
-
- [Architecture](./docs/architecture.md): runtime and subsystem overview.
|
|
279
|
-
- [Public API](./docs/public-api.md): stable commands, config, package entrypoints, assistant markup, and extension APIs.
|
|
280
|
-
- [Inbound Handlers](./docs/inbound.md): Telegram → Pi preprocessing.
|
|
281
|
-
- [Outbound Handlers](./docs/outbound.md): final text, voice, and artifact pipelines.
|
|
282
|
-
- [Command Templates](./docs/command-templates.md): portable command-template contract.
|
|
283
|
-
- [Callback Namespaces](./docs/callback-namespaces.md): callback interop for layered extensions.
|
|
284
|
-
- [Updates](./docs/updates.md): shared update interception.
|
|
285
|
-
- [Extension Sections](./docs/sections.md): Telegram extension sections platform for loading extensions that register UI surfaces.
|
|
286
|
-
- [Voice Integration](./docs/voice.md): voice reply policies, transparent interception, and provider extension API.
|
|
287
|
-
- [Locks](./docs/locks.md): singleton polling ownership.
|
|
288
|
-
- [UI Style](./docs/ui-style.md): inline button, toggle, tab, option-list, card, and dialog style guide.
|
|
232
|
+
Telegram is a companion surface around a live Pi runtime, not a second runtime.
|
|
289
233
|
|
|
290
|
-
##
|
|
234
|
+
## Documentation Map
|
|
291
235
|
|
|
292
|
-
-
|
|
293
|
-
-
|
|
294
|
-
-
|
|
236
|
+
- [Architecture](./docs/architecture.md) — runtime, domains, queue, transport, and Threaded Mode overview.
|
|
237
|
+
- [Public API](./docs/public-api.md) — package entrypoints and stable companion-extension contracts.
|
|
238
|
+
- [Inbound Handlers](./docs/inbound.md) — Telegram-to-Pi preprocessing pipelines.
|
|
239
|
+
- [Outbound Handlers](./docs/outbound.md) — final text/voice/file transformation and delivery.
|
|
240
|
+
- [Voice Integration](./docs/voice.md) — STT/TTS provider model and reply policies.
|
|
241
|
+
- [Extension Sections](./docs/sections.md) — Telegram-native companion UI surfaces.
|
|
242
|
+
- [Updates](./docs/updates.md) — update handler registry and callback interop.
|
|
243
|
+
- [Multi-Instance Bus](./docs/multi-instance-bus.md) — leader/follower routing in Threaded Mode.
|
|
244
|
+
- [Locks](./docs/locks.md) — singleton ownership and shared lock conventions.
|
|
245
|
+
- [UI Style](./docs/ui-style.md) — menu, emoji, labels, dialogs, and inline keyboard standards.
|
|
246
|
+
- [Callback Namespaces](./docs/callback-namespaces.md) — callback ownership and routing.
|
|
247
|
+
- [Command Templates](./docs/command-templates.md) — handler command-template conventions.
|
|
295
248
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
Third-party extensions that integrate with `pi-telegram`:
|
|
299
|
-
|
|
300
|
-
- [`pi-codex-usage`](https://github.com/llblab/pi-codex-usage) — Compact Codex subscription quota/status widget for the Pi statusline and the inline menu status text opened by `/start`.
|
|
301
|
-
|
|
302
|
-
```bash
|
|
303
|
-
pi install npm:@llblab/pi-codex-usage
|
|
304
|
-
```
|
|
249
|
+
The docs index lives at [docs/README.md](./docs/README.md).
|
|
305
250
|
|
|
306
|
-
|
|
251
|
+
## Development
|
|
307
252
|
|
|
308
253
|
```bash
|
|
309
|
-
|
|
254
|
+
npm run typecheck
|
|
255
|
+
npm test
|
|
256
|
+
npm run audit
|
|
257
|
+
npm run pack:check
|
|
310
258
|
```
|
|
311
259
|
|
|
312
|
-
|
|
260
|
+
Full validation:
|
|
313
261
|
|
|
314
262
|
```bash
|
|
315
|
-
|
|
263
|
+
npm run validate
|
|
316
264
|
```
|
|
317
265
|
|
|
318
|
-
|
|
266
|
+
Project context:
|
|
319
267
|
|
|
320
|
-
|
|
268
|
+
- [AGENTS.md](./AGENTS.md) — engineering and runtime conventions.
|
|
269
|
+
- [BACKLOG.md](./BACKLOG.md) — release-relevant open work.
|
|
270
|
+
- [CHANGELOG.md](./CHANGELOG.md) — completed delivery history.
|
package/docs/architecture.md
CHANGED
|
@@ -223,7 +223,7 @@ During active Telegram-owned turns, assistant message start/update hooks re-arm
|
|
|
223
223
|
|
|
224
224
|
### Rendering And Delivery
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
Rich Markdown is the model-answer membrane. Complete assistant replies send final Markdown directly as `InputRichMessage.markdown` through `sendRichMessage`, and guest replies use native Rich Markdown through `InputRichMessageContent` in `answerGuestQuery` results. Tool-call rows, reasoning/thinking blocks, menus, status rows, queue controls, settings, diagnostics, and other harness-owned surfaces stay on explicit Telegram HTML/plain rendering. Streaming previews may use `sendRichMessageDraft` when enabled and when draft delivery succeeds. The bridge still strips top-level assistant action comments before delivery and may split output only for Telegram transport limits.
|
|
227
227
|
|
|
228
228
|
Assistant delivery guarantees:
|
|
229
229
|
|
|
@@ -231,12 +231,12 @@ Assistant delivery guarantees:
|
|
|
231
231
|
- Before native Rich Markdown delivery, the bridge normalizes known Bot-API-fragile source forms without changing visible meaning, including space-after-marker blockquotes and dollar-prefixed ticker atoms that Telegram may otherwise treat as unterminated math.
|
|
232
232
|
- Quoted rich replies use Telegram `rich_message` blocks as the prompt-context source when available, so `[reply]` context receives rendered plain text instead of raw `InputRichMessage.markdown` fallback text.
|
|
233
233
|
- Long native Markdown replies are split only at Telegram Rich Message transport limits; oversized fenced code, display-math, and fully wrapped inline-formatting blocks are rewrapped per chunk so persisted Rich Markdown chunks remain structurally valid.
|
|
234
|
-
-
|
|
234
|
+
- When enabled, streaming previews pass structurally closed assistant Markdown prefixes through to `sendRichMessageDraft` with ownership checks, voice suppression, and serialized flushes. Unclosed inline spans, links, fenced code, comments, and display-math blocks are held back until a safe boundary exists. Draft failures are recorded and the failing frame is skipped instead of degrading to raw plain-message previews, because partial Markdown can be invalid while the final message remains valid.
|
|
235
235
|
- 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.
|
|
236
236
|
|
|
237
237
|
UI/compat rendering guarantees:
|
|
238
238
|
|
|
239
|
-
- 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
|
|
239
|
+
- Bridge-owned UI surfaces such as tool rows, reasoning/thinking blocks, commands, menus, status messages, queue controls, diagnostics, settings, and interactive sections use Telegram HTML/plain rendering helpers by default. These texts are authored for operational UI rather than model output, so explicit HTML/plain markup remains clearer, safer, and easier to maintain.
|
|
240
240
|
- 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.
|
|
241
241
|
|
|
242
242
|
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.
|
package/index.ts
CHANGED
|
@@ -487,6 +487,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
487
487
|
getMessageText: Replies.getAgentMessageText,
|
|
488
488
|
getDefaultReplyToMessageId: activeTurnRuntime.getReplyToMessageId,
|
|
489
489
|
sendDraft: nativeMarkdownDraftSender,
|
|
490
|
+
canSend: configControls.areRichDraftPreviewsEnabled,
|
|
490
491
|
sendMarkdownReply,
|
|
491
492
|
recordRuntimeEvent,
|
|
492
493
|
...replyTransport,
|
package/lib/config.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface TelegramConfig {
|
|
|
57
57
|
attachmentHandlers?: TelegramInboundHandlerConfig[];
|
|
58
58
|
outboundHandlers?: TelegramOutboundHandlerConfig[];
|
|
59
59
|
proactivePush?: boolean;
|
|
60
|
+
richDraftPreviews?: boolean;
|
|
60
61
|
voice?: {
|
|
61
62
|
replyMode?: "manual" | "mirror" | "always";
|
|
62
63
|
/** Whether to attach the provider's transcriptText as caption on voice messages */
|
|
@@ -259,6 +260,23 @@ export function createTelegramProactivePushSetter(
|
|
|
259
260
|
};
|
|
260
261
|
}
|
|
261
262
|
|
|
263
|
+
export function createTelegramRichDraftPreviewsChecker(
|
|
264
|
+
configStore: Pick<TelegramConfigStore, "get">,
|
|
265
|
+
): () => boolean {
|
|
266
|
+
return () => configStore.get().richDraftPreviews ?? false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function createTelegramRichDraftPreviewsSetter(
|
|
270
|
+
configStore: TelegramMutableConfigStore,
|
|
271
|
+
): (enabled: boolean) => Promise<void> {
|
|
272
|
+
return async (enabled) => {
|
|
273
|
+
await loadLatestTelegramConfig(configStore);
|
|
274
|
+
const config = { ...configStore.get(), richDraftPreviews: enabled };
|
|
275
|
+
configStore.set(config);
|
|
276
|
+
await configStore.persist(config);
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
262
280
|
export function createTelegramVoiceReplyModeGetter(
|
|
263
281
|
configStore: Pick<TelegramConfigStore, "get">,
|
|
264
282
|
): () => "manual" | "mirror" | "always" {
|
|
@@ -394,6 +412,10 @@ export function createTelegramConfigControls(
|
|
|
394
412
|
return {
|
|
395
413
|
isProactivePushEnabled: createTelegramProactivePushChecker(configStore),
|
|
396
414
|
setProactivePushEnabled: createTelegramProactivePushSetter(configStore),
|
|
415
|
+
areRichDraftPreviewsEnabled:
|
|
416
|
+
createTelegramRichDraftPreviewsChecker(configStore),
|
|
417
|
+
setRichDraftPreviewsEnabled:
|
|
418
|
+
createTelegramRichDraftPreviewsSetter(configStore),
|
|
397
419
|
getVoiceReplyMode: createTelegramVoiceReplyModeGetter(configStore),
|
|
398
420
|
isVoiceReplyModeConfigured:
|
|
399
421
|
createTelegramVoiceReplyModeConfiguredChecker(configStore),
|
package/lib/menu-settings.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
|
18
18
|
|
|
19
19
|
export interface TelegramSettingsStateDeps {
|
|
20
20
|
isProactivePushEnabled: () => boolean;
|
|
21
|
+
areRichDraftPreviewsEnabled: () => boolean;
|
|
21
22
|
getTimeInjectionMode: () => TelegramTimeMode;
|
|
22
23
|
getVoiceReplyMode: () => TelegramVoiceReplyMode;
|
|
23
24
|
isVoiceReplyModeConfigured: () => boolean;
|
|
@@ -25,6 +26,7 @@ export interface TelegramSettingsStateDeps {
|
|
|
25
26
|
|
|
26
27
|
export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
|
|
27
28
|
setProactivePushEnabled: (enabled: boolean) => Promise<void>;
|
|
29
|
+
setRichDraftPreviewsEnabled: (enabled: boolean) => Promise<void>;
|
|
28
30
|
setVoiceReplyMode: (
|
|
29
31
|
mode: TelegramVoiceReplyMode | undefined,
|
|
30
32
|
) => Promise<void>;
|
|
@@ -116,6 +118,8 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
116
118
|
|
|
117
119
|
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
118
120
|
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
|
|
121
|
+
export const RICH_DRAFT_PREVIEWS_SETTINGS_TITLE =
|
|
122
|
+
"<b>📝 Rich draft previews:</b>";
|
|
119
123
|
export const TIME_INJECTION_MODE_SETTINGS_TITLE =
|
|
120
124
|
"<b>🕒 Time injection mode:</b>";
|
|
121
125
|
export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
|
|
@@ -151,6 +155,17 @@ export function buildProactivePushSettingsText(
|
|
|
151
155
|
].join("\n");
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
export function buildRichDraftPreviewsSettingsText(enabled: boolean): string {
|
|
159
|
+
return [
|
|
160
|
+
`${RICH_DRAFT_PREVIEWS_SETTINGS_TITLE} <code>${enabled ? "on" : "off"}</code>`,
|
|
161
|
+
"",
|
|
162
|
+
"Show progressive Rich Markdown draft previews while the model is answering.",
|
|
163
|
+
"",
|
|
164
|
+
"<code>-</code> <code>off</code> (default): show native active status, then send the final Rich Markdown answer.",
|
|
165
|
+
"<code>-</code> <code>on</code>: also stream safe Rich Markdown draft previews before the final answer.",
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
154
169
|
export function buildVoiceReplyModeSettingsText(
|
|
155
170
|
mode: TelegramVoiceReplyMode,
|
|
156
171
|
configured = true,
|
|
@@ -185,6 +200,7 @@ export function buildTimeInjectionModeSettingsText(
|
|
|
185
200
|
|
|
186
201
|
export function buildTelegramSettingsMenuReplyMarkup(
|
|
187
202
|
proactivePushEnabled: boolean,
|
|
203
|
+
richDraftPreviewsEnabled: boolean,
|
|
188
204
|
voiceReplyMode: TelegramVoiceReplyMode,
|
|
189
205
|
timeInjectionMode: TelegramTimeMode,
|
|
190
206
|
sectionRegistry?: TelegramSectionRegistry,
|
|
@@ -217,6 +233,12 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
217
233
|
callback_data: "settings:open:time-injection",
|
|
218
234
|
},
|
|
219
235
|
],
|
|
236
|
+
[
|
|
237
|
+
{
|
|
238
|
+
text: `📝 Rich drafts: ${richDraftPreviewsEnabled ? "on" : "off"}`,
|
|
239
|
+
callback_data: "settings:open:rich-drafts",
|
|
240
|
+
},
|
|
241
|
+
],
|
|
220
242
|
[
|
|
221
243
|
{
|
|
222
244
|
text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
|
|
@@ -239,6 +261,7 @@ export async function openTelegramSettingsMenu<
|
|
|
239
261
|
buildTelegramSettingsMenuText(),
|
|
240
262
|
buildTelegramSettingsMenuReplyMarkup(
|
|
241
263
|
deps.isProactivePushEnabled(),
|
|
264
|
+
deps.areRichDraftPreviewsEnabled(),
|
|
242
265
|
deps.getVoiceReplyMode(),
|
|
243
266
|
deps.getTimeInjectionMode(),
|
|
244
267
|
sectionRegistry,
|
|
@@ -271,6 +294,26 @@ export function buildProactivePushSettingsReplyMarkup(
|
|
|
271
294
|
};
|
|
272
295
|
}
|
|
273
296
|
|
|
297
|
+
export function buildRichDraftPreviewsSettingsReplyMarkup(
|
|
298
|
+
enabled: boolean,
|
|
299
|
+
): TelegramSettingsMenuReplyMarkup {
|
|
300
|
+
return {
|
|
301
|
+
inline_keyboard: [
|
|
302
|
+
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
303
|
+
[
|
|
304
|
+
{
|
|
305
|
+
text: enabled ? "🟢 On" : "⚫️ On",
|
|
306
|
+
callback_data: "settings:set:rich-drafts:on",
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
text: enabled ? "⚫️ Off" : "🟡 Off",
|
|
310
|
+
callback_data: "settings:set:rich-drafts:off",
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
274
317
|
export function buildTimeInjectionModeSettingsReplyMarkup(
|
|
275
318
|
mode: TelegramTimeMode,
|
|
276
319
|
): TelegramSettingsMenuReplyMarkup {
|
|
@@ -320,6 +363,7 @@ export async function updateTelegramSettingsMenuMessage(
|
|
|
320
363
|
buildTelegramSettingsMenuText(),
|
|
321
364
|
buildTelegramSettingsMenuReplyMarkup(
|
|
322
365
|
deps.isProactivePushEnabled(),
|
|
366
|
+
deps.areRichDraftPreviewsEnabled(),
|
|
323
367
|
deps.getVoiceReplyMode(),
|
|
324
368
|
deps.getTimeInjectionMode(),
|
|
325
369
|
sectionRegistry,
|
|
@@ -338,6 +382,16 @@ export async function updateProactivePushSettingsMessage(
|
|
|
338
382
|
);
|
|
339
383
|
}
|
|
340
384
|
|
|
385
|
+
export async function updateRichDraftPreviewsSettingsMessage(
|
|
386
|
+
deps: TelegramSettingsMenuCallbackDeps,
|
|
387
|
+
): Promise<void> {
|
|
388
|
+
const enabled = deps.areRichDraftPreviewsEnabled();
|
|
389
|
+
await deps.updateSettingsMessage(
|
|
390
|
+
buildRichDraftPreviewsSettingsText(enabled),
|
|
391
|
+
buildRichDraftPreviewsSettingsReplyMarkup(enabled),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
341
395
|
export async function updateTimeInjectionModeSettingsMessage(
|
|
342
396
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
343
397
|
): Promise<void> {
|
|
@@ -375,6 +429,11 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
375
429
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
376
430
|
return true;
|
|
377
431
|
}
|
|
432
|
+
if (data === "settings:open:rich-drafts") {
|
|
433
|
+
await updateRichDraftPreviewsSettingsMessage(deps);
|
|
434
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
378
437
|
if (data === "settings:open:voice-reply") {
|
|
379
438
|
await updateVoiceReplyModeSettingsMessage(deps);
|
|
380
439
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
@@ -427,6 +486,19 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
427
486
|
return true;
|
|
428
487
|
}
|
|
429
488
|
}
|
|
489
|
+
if (
|
|
490
|
+
data === "settings:set:rich-drafts:on" ||
|
|
491
|
+
data === "settings:set:rich-drafts:off"
|
|
492
|
+
) {
|
|
493
|
+
const enabled = data.endsWith(":on");
|
|
494
|
+
await deps.setRichDraftPreviewsEnabled(enabled);
|
|
495
|
+
await updateRichDraftPreviewsSettingsMessage(deps);
|
|
496
|
+
await deps.answerCallbackQuery(
|
|
497
|
+
callbackQueryId,
|
|
498
|
+
`Rich draft previews ${enabled ? "enabled" : "disabled"}`,
|
|
499
|
+
);
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
430
502
|
if (
|
|
431
503
|
data === "settings:set:proactive:on" ||
|
|
432
504
|
data === "settings:set:proactive:off"
|
|
@@ -457,6 +529,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
457
529
|
{
|
|
458
530
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
|
|
459
531
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
532
|
+
areRichDraftPreviewsEnabled: deps.areRichDraftPreviewsEnabled,
|
|
460
533
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
461
534
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
462
535
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
@@ -475,6 +548,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
475
548
|
updateTelegramSettingsMenuMessage(
|
|
476
549
|
{
|
|
477
550
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
551
|
+
areRichDraftPreviewsEnabled: deps.areRichDraftPreviewsEnabled,
|
|
478
552
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
479
553
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
480
554
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
@@ -513,6 +587,18 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
513
587
|
);
|
|
514
588
|
return true;
|
|
515
589
|
}
|
|
590
|
+
if (
|
|
591
|
+
query.data === "settings:set:rich-drafts:on" ||
|
|
592
|
+
query.data === "settings:set:rich-drafts:off"
|
|
593
|
+
) {
|
|
594
|
+
const enabled = query.data.endsWith(":on");
|
|
595
|
+
await deps.setRichDraftPreviewsEnabled(enabled);
|
|
596
|
+
await deps.answerCallbackQuery(
|
|
597
|
+
query.id,
|
|
598
|
+
`Rich draft previews ${enabled ? "enabled" : "disabled"}`,
|
|
599
|
+
);
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
516
602
|
const hasTimeInjectionPrefix = query.data.startsWith(
|
|
517
603
|
"settings:set:time-injection:",
|
|
518
604
|
);
|
|
@@ -543,10 +629,12 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
543
629
|
}
|
|
544
630
|
return handleTelegramSettingsMenuCallbackAction(query.id, query.data, {
|
|
545
631
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
632
|
+
areRichDraftPreviewsEnabled: deps.areRichDraftPreviewsEnabled,
|
|
546
633
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
547
634
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
548
635
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
549
636
|
setProactivePushEnabled: deps.setProactivePushEnabled,
|
|
637
|
+
setRichDraftPreviewsEnabled: deps.setRichDraftPreviewsEnabled,
|
|
550
638
|
setVoiceReplyMode: deps.setVoiceReplyMode,
|
|
551
639
|
setTimeInjectionMode: deps.setTimeInjectionMode,
|
|
552
640
|
updateSettingsMessage: (text, replyMarkup) =>
|
package/lib/updates.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"BACKLOG.md",
|
|
43
43
|
"CHANGELOG.md",
|
|
44
44
|
"docs/",
|
|
45
|
-
"
|
|
45
|
+
"banner.png"
|
|
46
46
|
],
|
|
47
47
|
"exports": {
|
|
48
48
|
".": "./index.ts",
|
|
File without changes
|