@llblab/pi-telegram 0.17.5 → 0.18.1

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.
Files changed (61) hide show
  1. package/AGENTS.md +67 -32
  2. package/BACKLOG.md +59 -19
  3. package/CHANGELOG.md +42 -15
  4. package/README.md +63 -35
  5. package/docs/README.md +3 -1
  6. package/docs/architecture.md +55 -23
  7. package/docs/callback-namespaces.md +1 -1
  8. package/docs/inbound.md +1 -1
  9. package/docs/locks.md +0 -2
  10. package/docs/multi-instance-bus.md +410 -0
  11. package/docs/outbound.md +4 -3
  12. package/docs/public-api.md +12 -10
  13. package/docs/sections.md +2 -2
  14. package/docs/ui-style.md +76 -0
  15. package/index.ts +789 -32
  16. package/lib/bindings.ts +68 -12
  17. package/lib/bus-api.ts +314 -0
  18. package/lib/bus-follower.ts +853 -0
  19. package/lib/bus-leader.ts +915 -0
  20. package/lib/bus.ts +866 -0
  21. package/lib/command-templates.ts +9 -11
  22. package/lib/commands.ts +133 -47
  23. package/lib/config.ts +53 -5
  24. package/lib/lifecycle.ts +23 -7
  25. package/lib/locks.ts +230 -66
  26. package/lib/media.ts +30 -2
  27. package/lib/menu-model.ts +48 -17
  28. package/lib/menu-queue.ts +51 -20
  29. package/lib/menu-settings.ts +9 -5
  30. package/lib/menu-status.ts +3 -0
  31. package/lib/menu-thinking.ts +3 -0
  32. package/lib/menu.ts +67 -26
  33. package/lib/outbound-attachments.ts +102 -17
  34. package/lib/outbound-buttons.ts +6 -2
  35. package/lib/outbound-voice.ts +31 -11
  36. package/lib/outbound.ts +6 -4
  37. package/lib/ownership.ts +119 -0
  38. package/lib/pi.ts +26 -3
  39. package/lib/polling.ts +477 -7
  40. package/lib/preview.ts +141 -88
  41. package/lib/prompt-templates.ts +3 -3
  42. package/lib/prompts.ts +80 -30
  43. package/lib/queue.ts +193 -91
  44. package/lib/rendering.ts +0 -25
  45. package/lib/replies.ts +187 -55
  46. package/lib/routing.ts +1673 -9
  47. package/lib/runtime-log.ts +123 -0
  48. package/lib/runtime.ts +84 -12
  49. package/lib/sections.ts +28 -21
  50. package/lib/setup.ts +9 -2
  51. package/lib/status.ts +532 -9
  52. package/lib/sync.ts +618 -0
  53. package/lib/target.ts +49 -0
  54. package/lib/telegram-api.ts +409 -41
  55. package/lib/text-groups.ts +5 -1
  56. package/lib/thread-reconciler.ts +915 -0
  57. package/lib/threads.ts +2205 -0
  58. package/lib/turns.ts +48 -3
  59. package/lib/updates.ts +355 -32
  60. package/package.json +24 -2
  61. package/docs/telegram-bot-api-rich-messages.md +0 -890
package/README.md CHANGED
@@ -2,14 +2,24 @@
2
2
 
3
3
  ![pi-telegram screenshot](screenshot.png)
4
4
 
5
- **Telegram runtime adapter for π.**
5
+ **Telegram runtime adapter for Pi.**
6
6
 
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. It is also a voice-provider platform: companion extensions can supply Telegram transcription and synthesis providers while `pi-telegram` keeps ownership of transport, queueing, and reply policy.
7
+ `pi-telegram` turns a private Telegram DM into a session-local operator console for Pi. It admits work, preserves context, streams readable replies, keeps busy sessions usable through queues, and turns assistant-authored intent into native Telegram artifacts.
8
8
 
9
- The product shape is a mobile companion for a live Pi session: start work in the terminal, then continue from Telegram on the couch or outside. It is not a remote terminal, PTY supervisor, or process launcher, and it intentionally avoids pretending to own Pi's interactive TUI.
9
+ The product shape is a mobile companion for a live Pi session: start work in the terminal, then continue from Telegram on the couch or outside. It is not a remote terminal, PTY supervisor, or process launcher. Companion extensions can add commands, sections, status rows, handlers, and voice providers while `pi-telegram` keeps ownership of transport, queueing, and reply policy.
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
+
13
23
  ## Install
14
24
 
15
25
  From npm:
@@ -33,9 +43,9 @@ pi install git:github.com/llblab/pi-telegram
33
43
  3. Pick a name and username
34
44
  4. Copy the bot token
35
45
 
36
- ### 2. Configure the bot token in π
46
+ ### 2. Configure the bot token in Pi
37
47
 
38
- Start π, then run:
48
+ Start Pi, then run:
39
49
 
40
50
  ```bash
41
51
  /telegram-setup
@@ -43,13 +53,13 @@ Start π, then run:
43
53
 
44
54
  Paste your bot token when prompted. If a bot token is already saved in `~/.pi/agent/telegram.json`, the setup prompt shows that stored value by default. Otherwise it prefills from the first configured environment variable in `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_KEY`, `TELEGRAM_TOKEN`, or `TELEGRAM_KEY`. The saved config file is written atomically with private `0600` permissions.
45
55
 
46
- ### 3. Connect this π session
56
+ ### 3. Connect this Pi session
47
57
 
48
58
  ```bash
49
59
  /telegram-connect
50
60
  ```
51
61
 
52
- The adapter is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records only external control/polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, inherited child sessions do not start polling unless they take ownership, and same-`cwd` restarts resume automatically. Local queue and reply state stay per Pi instance, so an instance that loses Telegram control still finishes work it already accepted.
62
+ The adapter is session-local: only one Pi instance polls Telegram at a time. In classic mode, `/telegram-connect` records external control/polling ownership in `~/.pi/agent/locks.json`. When Telegram private-chat Threaded Mode is available for the bot, `/telegram-connect` uses the local Telegram organism automatically: the first live instance becomes leader, later live instances register as followers instead of taking over while the leader heartbeat is healthy. Local queue and reply state stay per Pi instance, so an instance that loses Telegram control still finishes work it already accepted.
53
63
 
54
64
  ### 4. Pair your Telegram account
55
65
 
@@ -60,72 +70,90 @@ The first user to message the bot becomes the exclusive owner of the adapter. Me
60
70
 
61
71
  ### Environment-only configuration
62
72
 
63
- 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:
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:
64
74
 
65
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.
66
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.
67
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`.
68
79
  - **Inbound file limit**: `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES` changes the default 50 MiB Telegram download limit.
69
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.
70
81
 
71
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.
72
83
 
73
- Set these variables before launching π. Some transport defaults (notably Telegram temp directory and inbound/outbound byte-limit constants) are intentionally captured when the extension modules load, while setup-token defaults and agent-dir lookups used by config/locks are read through their runtime helpers.
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.
74
87
 
75
88
  ## Use
76
89
 
77
- Once paired, chat with your bot in Telegram. Text, images, files, replies, edits, media groups, and configured handler output are forwarded into π as Telegram-originated turns.
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.
78
91
 
79
92
  What it feels like:
80
93
 
81
- - Start work in the terminal, walk away, and keep supervising the same live π session from Telegram.
82
- - Open `/start` and get a Telegram control panel for the running π session: status, prompt templates, model, thinking, settings, and queue.
83
- - Fire off three tasks while π is busy. They become visible queue items instead of terminal noise.
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.
84
97
  - Open Queue from the menu, inspect waiting work, delete stale prompts, or move important work forward.
85
98
  - Switch models from Telegram mid-run; the adapter schedules a safe continuation instead of tearing state apart.
86
- - Send a voice note; a configured inbound handler or registered STT provider transcribes it; π answers in the same chat.
87
- - Drop a screenshot and ask, "what is broken here?" The image payload reaches π with the local file context.
88
- - Ask for a generated file; when π calls `telegram_attach`, the artifact returns with the active Telegram reply or is sent directly to the paired/default chat from local work.
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.
89
102
 
90
103
  ### Telegram controls
91
104
 
92
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.
93
106
 
94
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.
95
- - **`/compact`**: Ask for inline confirmation, then start session compaction when the session is idle; Telegram shows the native typing indicator while manual or automatic compaction is running.
96
- - **`/next`**: Dispatch the next queued turn, aborting π first if needed.
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.
97
110
  - **`/continue`**: Enqueue a priority `continue` prompt.
98
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.
99
112
  - **`/stop`**: Abort the active run and clear waiting Telegram queue items.
100
113
 
101
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.
102
115
 
103
- 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.
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.
104
117
 
105
- ### π commands
118
+ ### Pi commands
106
119
 
107
- Run these inside π, not Telegram:
120
+ Run these inside Pi, not Telegram:
108
121
 
109
122
  - **`/telegram-setup`**: Configure or update the Telegram bot token.
110
- - **`/telegram-connect`**: Start polling Telegram updates in the current π session and acquire the singleton lock.
111
- - **`/telegram-disconnect`**: Stop polling in the current π session and release the singleton lock.
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.
112
125
  - **`/telegram-status`**: Inspect adapter status, connection, polling, execution, queue, and recent redacted runtime/API failure events.
113
126
 
114
127
  ### Files and artifacts
115
128
 
116
129
  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.
117
130
 
118
- If you ask π for a generated file, π can call `telegram_attach`: during a Telegram-originated turn the adapter sends it with the next Telegram reply, and during local/TUI work it sends directly to the paired/default chat or explicit `chat_id`. Local work can also use `telegram_message` when you explicitly ask the agent to push a Markdown text message to Telegram; embedded `telegram_button` comments are parsed and attached to that message. Direct local/TUI delivery requires the current π instance to own `/telegram-connect`; if the lock belongs elsewhere, take over before sending. Outbound attachments default to a 50 MiB limit. Environment variables for both limits are listed in [Environment-only configuration](#environment-only-configuration).
131
+ If you ask Pi for a generated file, Pi can call `telegram_attach`: during a Telegram-originated turn the adapter sends it with the next Telegram reply, and during local/TUI work it sends directly to the paired/default chat, a registered follower's assigned thread, or explicit `chat_id` plus optional `thread_id`. Local work can also use `telegram_message` when you explicitly ask the agent to push a Markdown text message to Telegram; embedded `telegram_button` comments are parsed and attached to that message. Direct local/TUI delivery requires the current Pi instance to own `/telegram-connect`, or to be registered with an explicitly enabled multi-instance bus so it can route through the leader; if neither is true, take over or enable/register with the bus before sending. Outbound attachments default to a 50 MiB limit. Environment variables for both limits are listed in [Environment-only configuration](#environment-only-configuration).
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.
119
147
 
120
148
  ## Core features
121
149
 
122
150
  ### Operator menu and controls
123
151
 
124
- 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 π session running without touching the terminal.
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.
125
153
 
126
154
  ### Queue runtime
127
155
 
128
- 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. Queue processing and reply delivery stay local to the Pi instance that accepted the work, even if `/telegram-connect` later moves elsewhere.
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.
129
157
 
130
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:
131
159
 
@@ -144,9 +172,9 @@ Telegram replies to earlier text or caption messages are forwarded as `[reply]`
144
172
 
145
173
  ### Inbound handlers and STT providers
146
174
 
147
- `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`.
175
+ `telegram.json` can define ordered `inboundHandlers` for Telegram → Pi 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`.
148
176
 
149
- 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. Extensions can also register programmatic inbound handlers; full voice extensions can register transcription providers. Explicit `inboundHandlers` and legacy `attachmentHandlers` run first, then programmatic inbound handlers, then registered STT providers as fallback for voice/audio files.
177
+ A practical voice setup is simple: Telegram `.ogg` arrives, STT runs locally or through your chosen command, stdout is injected as `[outputs]`, and Pi receives the result as usable prompt context. Extensions can also register programmatic inbound handlers; full voice extensions can register transcription providers. Explicit `inboundHandlers` and legacy `attachmentHandlers` run first, then programmatic inbound handlers, then registered STT providers as fallback for voice/audio files.
150
178
 
151
179
  ```json
152
180
  {
@@ -175,7 +203,7 @@ A practical voice setup is simple: Telegram `.ogg` arrives, STT runs locally or
175
203
 
176
204
  ### Outbound handlers, voice synthesis providers, and buttons
177
205
 
178
- 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, lists, and indented examples. Do not use JSON button specs, inline comments after visible text, or standalone button tool calls; write normal Markdown plus hidden comments, and add visible parent text if buttons would otherwise be the only output.
206
+ Assistant replies can include hidden outbound blocks. `telegram_voice` and `telegram_button` are not Pi 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, lists, and indented examples. Do not use JSON button specs, inline comments after visible text, or standalone button tool calls; write normal Markdown plus hidden comments, and add visible parent text if buttons would otherwise be the only output.
179
207
 
180
208
  Prompt guidance is context-aware: unconfigured sessions receive no Telegram suffix, local/TUI prompts only get explicit direct-delivery guidance, and Telegram-originated turns get the full phone-width output and action-comment contract.
181
209
 
@@ -208,13 +236,13 @@ If `telegram.json` explicitly sets a valid `voice.replyMode`, prompts include co
208
236
 
209
237
  In `mirror` and `always` modes, the bridge transparently intercepts agent text responses and routes them through the outbound voice pipeline. Configured `outboundHandlers` with `type: "voice"` run first in their configured order; zero-config registered synthesis providers run after them as progressive fallbacks. If several synthesis providers are installed, they are tried in registration order and the first one that returns a valid `.ogg`/`.opus` artifact handles the reply; `undefined`, errors, or invalid output fall through to the next provider. If every voice generator fails, the bridge falls back to sending the text reply instead.
210
238
 
211
- Voice synthesis provider extensions register TTS backends at runtime through public API domain subpaths. Multiple synthesis providers can be registered; stable provider registrations pass a durable abstract id owned by the companion extension, such as `"@scope/voice-provider/tts"`. The bridge tries configured `type: "voice"` handlers first, then programmatic handlers, then registered synthesis providers in registration order until one succeeds. Providers and handlers receive the text to synthesize and optional `lang`/`rate` hints from `<!-- telegram_voice -->` markup or the automatic interception path. Voice delivery must produce `.ogg` or `.opus` files.
239
+ Voice synthesis provider extensions register TTS backends at runtime through public API domain subpaths. Providers use durable ids, receive the text plus optional `lang`/`rate` hints, and must return `.ogg` or `.opus` artifacts. The bridge tries configured `type: "voice"` handlers first, then programmatic handlers, then registered synthesis providers in registration order.
212
240
 
213
- Provider code examples, transcript-caption behavior, and diagnostics patterns live in [Voice Integration](./docs/voice.md) and [Public API](./docs/public-api.md). The important boundary is: providers own TTS and any private optimization, while pi-telegram owns reply policy, prompt context, fallback ordering, and Telegram transport.
241
+ Provider examples and diagnostics live in [Voice Integration](./docs/voice.md) and [Public API](./docs/public-api.md). Boundary: providers own TTS; `pi-telegram` owns reply policy, prompt context, fallback ordering, and Telegram transport.
214
242
 
215
243
  ### Extension interop
216
244
 
217
- 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 handler on the shared update registry.
245
+ Unknown inline-button callbacks are forwarded to Pi 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 handler on the shared update registry.
218
246
 
219
247
  ### Extension Sections
220
248
 
@@ -224,11 +252,11 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/sections` and retur
224
252
 
225
253
  ### Proactive push
226
254
 
227
- `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 and this π instance currently owns `/telegram-connect`. Non-owners skip proactive delivery instead of pushing unrelated local/headless results through a bot they no longer control. 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.
255
+ `telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to Telegram when no Telegram turn is active and this Pi instance currently owns `/telegram-connect` or is registered as a Threaded Mode follower. Non-owners skip proactive delivery instead of pushing unrelated local/headless results through a bot they no longer control. 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.
228
256
 
229
257
  ### Time context
230
258
 
231
- `telegram.json` can opt into a compact `[time]` line in Telegram-originated prompts so π has a wall-clock reference for requests such as "today", "now", or scheduling. It is hidden by default and uses the system timezone; the mode can also be changed from Settings → `🕒 Time injection`.
259
+ `telegram.json` can opt into a compact `[time]` line in Telegram-originated prompts so Pi has a wall-clock reference for requests such as "today", "now", or scheduling. It is hidden by default and uses the system timezone; the mode can also be changed from Settings → `🕒 Time injection`.
232
260
 
233
261
  ```json
234
262
  {
@@ -249,7 +277,7 @@ Modes are `hidden`, `always`, and `interval`. `hidden` means no time line is add
249
277
  - [Documentation Index](./docs/README.md): technical docs hub.
250
278
  - [Architecture](./docs/architecture.md): runtime and subsystem overview.
251
279
  - [Public API](./docs/public-api.md): stable commands, config, package entrypoints, assistant markup, and extension APIs.
252
- - [Inbound Handlers](./docs/inbound.md): Telegram → π preprocessing.
280
+ - [Inbound Handlers](./docs/inbound.md): Telegram → Pi preprocessing.
253
281
  - [Outbound Handlers](./docs/outbound.md): final text, voice, and artifact pipelines.
254
282
  - [Command Templates](./docs/command-templates.md): portable command-template contract.
255
283
  - [Callback Namespaces](./docs/callback-namespaces.md): callback interop for layered extensions.
package/docs/README.md CHANGED
@@ -8,12 +8,14 @@ Living index of project documentation in `/docs`.
8
8
 
9
9
  - [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, queueing model, native Rich Markdown delivery, UI/compat rendering, and interactive controls
10
10
  - [public-api.md](./public-api.md) — Stable public API map: package entrypoints, commands, config, assistant markup, extension APIs, smoke examples, and compatibility boundaries
11
- - [telegram-bot-api-rich-messages.md](./telegram-bot-api-rich-messages.md) — Local Bot API Rich Messages reference used for native Rich Markdown delivery work
11
+ - [../.agents/skills/telegram-bot/SKILL.md](../.agents/skills/telegram-bot/SKILL.md) — Agent-facing Telegram Bot API lookup skill backed by a local full Bot API reference
12
+ - [../.agents/skills/domain-dag/SKILL.md](../.agents/skills/domain-dag/SKILL.md) — Project-local Domain DAG architecture skill and validator guidance
12
13
  - [command-templates.md](./command-templates.md) — Portable command-template standard core
13
14
  - [inbound.md](./inbound.md) — Local `pi-telegram` inbound text/media handler bus, programmatic inbound handlers, registered STT provider fallbacks, legacy `attachmentHandlers` compatibility, placeholders, and fallbacks
14
15
  - [outbound.md](./outbound.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, voice synthesis provider fallback priority, artifact outputs, and callback routing
15
16
  - [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
16
17
  - [updates.md](./updates.md) — Update classification and runtime handler registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
18
+ - [multi-instance-bus.md](./multi-instance-bus.md) — Optional multi-instance Telegram bus architecture: leader/follower routing, thread targets, instance slots, manual follower registration, and recovery semantics
17
19
  - [sections.md](./sections.md) — Telegram Extension Sections Standard: registration contract, context ports, callback routing, navigation hierarchy, and demo reference for pi extensions that want Telegram UI surfaces
18
20
  - [voice.md](./voice.md) — Voice integration guide: detection, reply policy, STT/TTS provider registration, provider-owned conversion, and transparent interception
19
21
  - [locks.md](./locks.md) — Shared `locks.json` standard for singleton extension ownership
@@ -2,11 +2,11 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- `pi-telegram` is a session-local π extension that binds one Telegram DM to one running π session. It owns the Telegram bridge boundary:
5
+ `pi-telegram` is a session-local Pi extension that binds one Telegram DM to one running Pi session. It owns the Telegram bridge boundary:
6
6
 
7
7
  - Poll Telegram updates and enforce single-user pairing.
8
- - Translate Telegram text, callbacks, media, and files into π turns.
9
- - Stream previews and deliver final π responses back to Telegram.
8
+ - Translate Telegram text, callbacks, media, and files into Pi turns.
9
+ - Stream previews and deliver final Pi responses back to Telegram.
10
10
  - Provide Telegram-native controls for queueing, model/thinking/settings menus, compaction, abort/stop, prompt templates, reactions, and outbound artifacts.
11
11
 
12
12
  The bridge is a mobile companion for a live Pi session, not a remote terminal. It should let an operator start work in the TUI and continue supervising from Telegram, while staying inside Pi's extension-facing contracts.
@@ -20,10 +20,11 @@ This document is the architectural map. Focused behavior standards live in sibli
20
20
  - [Updates](./updates.md) — update classification, default-routing plans, and raw Telegram update interception.
21
21
  - [Voice Integration](./voice.md) — voice reply policy and STT/TTS provider surface.
22
22
  - [Command Templates](./command-templates.md) — shell-free command-template contract.
23
+ - [Telegram Multi-Instance Bus](./multi-instance-bus.md) — Threaded Mode bus leadership, Telegram UI thread targets, instance identity, and leader/follower routing.
23
24
 
24
25
  ## Runtime Topology
25
26
 
26
- `index.ts` is the only composition root. It wires live π ports, Telegram Bot API ports, session-local stores, lifecycle hooks, and domain runtimes. Reusable logic lives in flat `/lib/*.ts` domain modules rather than a deep local module tree.
27
+ `index.ts` is the only composition root. It wires live Pi ports, Telegram Bot API ports, session-local stores, lifecycle hooks, and domain runtimes. It should operate at high-level domain-runtime boundaries: non-trivial Threaded Mode capability decisions, leader/follower recovery, sync-slice bookkeeping, manual thread cleanup, and bus routing policies belong in their owning `/lib` domains. Reusable logic lives in flat `/lib/*.ts` domain modules rather than a deep local module tree.
27
28
 
28
29
  ### Extension Boundary Vs Supervisor Control
29
30
 
@@ -50,8 +51,12 @@ The repository uses a **Flat Domain DAG**:
50
51
  - `index.ts`: composition root for live ports, session state, transport adapters, and lifecycle registration.
51
52
  - `api`: Bot API helpers, retries, uploads/downloads, temp cleanup, byte limits, chat actions, lazy token clients, and API error recording.
52
53
  - `config` / `setup`: `telegram.json`, bot token setup, first-user pairing, authorization, env fallback, atomic persistence, and live config accessors.
53
- - `locks` / `polling`: singleton polling ownership, takeover/restart behavior, long-poll controller state, offset persistence, and poll-loop wiring.
54
- - `updates` / `routing`: update classification, authorization planning, callbacks, edited messages, reactions, and inbound route composition.
54
+ - `locks` / `polling`: singleton lock storage and status labels, lock-aware polling lifecycle/takeover/follower-registration orchestration, classic-vs-Threaded polling switching, Threaded Mode capability probes/monitoring, long-poll controller state, offset persistence, and poll-loop wiring.
55
+ - `bus` / `bus-api` / `bus-leader` / `bus-follower` / `ownership` / `target`: Threaded Mode multi-instance bus contracts, local leader/follower IPC, leader-only orchestration, follower-side manual registration/session runtime, follower-routed Bot API calls, live message ownership, and `{ chatId, threadId? }` target identity. `bus` owns shared protocol and local IPC primitives; `bus-leader` owns leader runtime, leader envelope handling, activation scheduling, and leader polling/server/prune orchestration; `bus-follower` owns this Pi instance's follower-side registration, heartbeat, forwarded-update receiver, and routed API caller without any process spawning.
56
+ - `sync`: demand-driven Telegram reconciliation and local assumption policy. It does not own a complete Telegram bot read-model; Bot API lacks a complete topic/thread listing surface. It owns sync slices, invalidation triggers, observation intake, status/debug freshness, and reconciliation scheduling across bot identity, pairing assumptions, live target bindings, reservations, and transport health after meaningful observable signals. It should call narrower domain primitives rather than letting `index.ts`, `threads`, or `status` accumulate cross-cutting reconciliation policy.
57
+ - `thread-reconciler`: Threaded Mode control-plane planning for Telegram thread/tab lifecycle. It owns the reconciliation state machine (`stable`, `provisioning`, `sync-required`, `cleanup-required`), pure plans, proof-before-delete rules, pending-provision protection, fresh-creation grace windows, leader-epoch checks, and the single policy authority for destructive thread cleanup actions. It excludes live Telegram API calls, inbound routing, menu rendering, and direct persistence.
58
+ - `threads`: Telegram UI thread/tab binding state mapped to Bot API `message_thread_id` / `ForumTopic` transport. Owns current thread target state, slot allocation from the current extension state, baked compact thread-name selection, current binding persistence, and primitive thread provision helpers. It should not persist stale/offline/failed target history, own destructive cleanup policy, grow into the general Telegram synchronization domain, or expose a rename tool.
59
+ - `updates` / `routing`: update classification, authorization planning, callbacks, edited messages, reactions, target-owner forwarding, and inbound route composition.
55
60
  - `media` / `text-groups` / `time-injection` / `turns` / `inbound`: inbound text/media/file extraction, rich-message reply-context plaintext recovery, media-group debounce, long-text coalescing, optional `[time]` context, handler execution, and prompt-turn assembly/editing.
56
61
  - `queue`: queue item contracts, lane admission/order, readiness gates, mutations, dispatch runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
57
62
  - `runtime`: session-local coordination primitives: counters, flags, setup guard, abort handler, typing timers, dispatch flags, and reset binding.
@@ -63,7 +68,7 @@ The repository uses a **Flat Domain DAG**:
63
68
  - `outbound`: outbound text transformations, voice/button artifact delivery, and generated callback actions.
64
69
  - `outbound-attachments`: `telegram_attach`, queued outbound files, stat/limit checks, and photo/document delivery classification.
65
70
  - `status`: status bar/status-message rendering, queue-lane summaries, redacted event ring, and grouped diagnostics.
66
- - `lifecycle` / `prompts` / `prompt-templates` / `pi`: π hook registration, Telegram prompt guidance, prompt-template discovery/expansion, and centralized direct π SDK imports.
71
+ - `lifecycle` / `prompts` / `prompt-templates` / `pi`: Pi hook registration, Telegram prompt guidance, prompt-template discovery/expansion, and centralized direct Pi SDK imports.
67
72
  - `command-templates`: shell-free command-template helpers, composition expansion, placeholder substitution, executable resolution, warnings, and retry/timeout semantics.
68
73
 
69
74
  ### Guarded Invariants
@@ -71,7 +76,7 @@ The repository uses a **Flat Domain DAG**:
71
76
  Architecture invariant tests protect:
72
77
 
73
78
  - Acyclic local imports.
74
- - Direct π SDK imports centralized in the `pi` adapter.
79
+ - Direct Pi SDK imports centralized in the `pi` adapter.
75
80
  - `index.ts` as a composition root without local runtime adapter logic.
76
81
  - Runtime state isolation from local domain imports.
77
82
  - Structural leaf-domain isolation.
@@ -99,8 +104,8 @@ Telegram configuration lives in `~/.pi/agent/telegram.json`. Polling ownership l
99
104
  ### Runtime Ownership
100
105
 
101
106
  - `/telegram-connect` acquires or moves singleton polling ownership before polling starts.
102
- - `/telegram-disconnect` stops polling and releases ownership.
103
- - Session start resumes polling only when the existing lock already points at the current `pid`/`cwd`, or when a stale same-`cwd` lock can be safely replaced after process restart.
107
+ - `/telegram-disconnect` stops polling and releases ownership. In Threaded Mode it first tears down the disconnecting instance's bound Telegram thread: leaders delete their own thread directly, and followers ask the leader to delete their assigned thread through scoped bus API before unregistering.
108
+ - Session start schedules Telegram polling resume asynchronously only when the existing lock already points at the current `pid`/`cwd`, or when a stale same-`cwd` lock can be safely replaced after process restart. Startup and `/resume` should not wait on Telegram leader election, Bot API probes, poller handoff, or thread reconciliation before restoring the Pi session.
104
109
  - Pi `print`/`json` run modes stay passive: they do not start or resume Telegram polling even if a lock is present. Older Pi runtimes without `ctx.mode` keep the previous compatibility behavior.
105
110
  - Inherited child sessions that see the same `telegram.json` but do not own the `pid`/`cwd` lock must not auto-start polling or call `getUpdates` unless the operator force-takes ownership.
106
111
  - Session replacement suspends polling/watchers without releasing ownership so the next session-start hook in the same process can resume.
@@ -111,6 +116,32 @@ Telegram configuration lives in `~/.pi/agent/telegram.json`. Polling ownership l
111
116
 
112
117
  Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
113
118
 
119
+ ### Threaded Mode Multi-Instance Bus
120
+
121
+ Telegram private-chat Threaded Mode is the public switch for multi-instance Telegram operation. Classic single-DM polling is the base mode. When Telegram private-chat threads are available for the bot, the bridge enables the local leader/follower bus automatically; when threads are unavailable or later disabled, the bridge returns to classic single-DM polling as a first-class mode.
122
+
123
+ When Threaded Mode is active, the current polling owner is also the Telegram bus leader. The leader owns the local bus endpoint (Unix-domain socket on Unix-like platforms, named pipe on native Windows), polls `getUpdates`, performs direct Bot API calls, records follower heartbeats, prunes stale followers, and provisions Telegram UI thread targets through live runtime/bus state. Follower liveness is intentionally fast because heartbeat traffic is local IPC: followers heartbeat every `1s`, the leader treats them as stale after `2s`, and the prune loop runs every `1s` so stopped followers are detected promptly while active forwarded updates/API calls still refresh liveness. Heartbeat pruning is silent liveness bookkeeping: it preserves the follower thread binding and does not send a Telegram-visible disconnected notice, because the common cause may be leader reload or IPC handoff rather than a dead follower. `tmp/telegram/logs.jsonl` is a session-local redacted runtime evidence stream for race debugging; it resets on extension start and runtime scope changes, and must not become routing/provisioning authority. `tmp/telegram/state.json` is an extension+bot observable/debug snapshot aligned with status diagnostics: `source: "snapshot"` and `writtenAtMs` mark it as observational, not authoritative. Fresh capability observations may skip redundant startup probes, but stale snapshots re-probe before suppressing bus/thread behavior. Top-level `bot` mirrors bot-wide capabilities such as thread mode, `runtime` describes process role/status, `liveRoster` mirrors followers/current targets/reservations, `diagnostics` mirrors recent status/debug signals including the latest thread-reconciler phase/counts, `threads` stores current routeable bindings, TTL-bounded reservations explain short-lived slot collision guards, and TTL-pruned `pendingProvisions` protects in-flight topic creation slots from cleanup/allocation races. Fresh provisioning writes pending state before the Bot API create call, adds the returned target to the pending record, persists a `starting` binding, then promotes it to `active` and clears pending state. If final binding persistence fails after Telegram returns a thread id, the targeted pending provision remains as cleanup/retry evidence. Once targeted pending provisions expire, they are retained for `thread-reconciler` close/delete cleanup and pending scratchpad removal after a successful cleanup apply; untargeted expired pending records can prune without cleanup because no Telegram thread id exists. Runtime events coalesce status-snapshot writes so transient bus/API/update failures remain inspectable even when the operator has not opened `/telegram-status`. The bridge must not keep a durable `telegram-targets.json` target history; stale/offline/failed thread observations are pruned instead of reused. Previous-process leader bindings that still probe alive become reservations/collision guards, not routeable active threads, so a reloaded leader can take the next free slot without duplicating the same visible tab name. The thread chat is always the private bot DM with the paired owner (`allowedUserId`). In Telegram private-chat Threaded Mode, the leader creates/reuses its own thread before polling — it is a real bound instance, not a dispatcher. Followers authenticate bus envelopes with the leader-minted capability secret stored in the active lock entry. Leader lock entries also carry a stable `leaderEpoch` minted on acquisition and preserved across heartbeat refreshes; leader-owned cleanup/provisioning plans stamp that epoch, and Thread Reconciler apply skips destructive work if leadership has moved on before side effects run. Followers own their own Pi session state, queue, active turns, previews, menus, and lifecycle hooks, but route allowlisted, target-scoped Telegram API calls through the leader. When a follower promotes after heartbeat loss, status/state diagnostics expose only the transient `electing` lifecycle phase; stable `leader`/`follower` identity stays in the bus role so diagnostics do not duplicate role state. The TUI status bar and `/telegram-status` report `leader` or `follower` role so a registered follower is not shown as generically disconnected.
124
+
125
+ Follower binding is manual and process-first: the operator starts another Pi process, then runs `/telegram-connect`; only then does that process register as a follower with an instance-scoped internal binding identity and cause the leader to create/reuse a thread for it. Telegram does not expose `/thread`, auto-spawn arbitrary unbound threads, or launch hidden follower subprocesses. In Threaded Mode, `/telegram-connect` does not offer manual takeover while a live leader exists; takeover is reserved for stale-leader election/recovery. Leadership remains an ephemeral transport role that another live follower can take over after stale heartbeat detection.
126
+
127
+ ### Unbound Thread Detection
128
+
129
+ When Threaded Mode is enabled, writing a message in the `All` tab can create a new thread without an existing instance binding. The bridge detects this during update execution: if a message from the owner has a `message_thread_id` that no instance owns, the message is routed to the unbound-thread handler instead of the leader's normal message handler. In the default runtime, this handler first reclaims the thread for the leader when the leader has no active bound thread, assigns the current leader thread identity, persists the active binding, and serves the prompt locally. If the leader already has an active thread, the handler preserves the prompt in the source Telegram thread and shows a target-thread chooser; explicit successful routing may later close/delete only extra confirmed source threads through `thread-reconciler` proof-before-delete planning and stale-epoch fencing. Unknown `forum_topic_created` service events are recorded as observations and are not destructive cleanup proof, because Telegram can deliver creation events before local provisioning/binding writes become visible across reloads. If Threaded Mode is unavailable, the message is processed normally through classic routing.
130
+
131
+ Threadless messages from `All` are not routed as prompts once bound threads exist, because `All` cannot identify the owning Pi instance. Known commands from `All` open a compact live-target chooser, while ordinary threadless prompts get guidance to use a bound Pi thread tab. This preserves a safe default after the operator closes every thread while still preventing later accidental empty tabs from black-holing prompts or spawning hidden Pi processes. The operator-facing path for another instance is visible manual follower registration: start Pi in a terminal, then run `/telegram-connect`.
132
+
133
+ The routing identity split is deliberate:
134
+
135
+ - Live routing owner: `instanceId` from the currently registered follower/leader runtime. A live instance may have only one active bound thread; provisioning a new target removes older current-state bindings for the same `instanceId` and closes duplicate Telegram threads when possible.
136
+ - Current binding owner: explicit `owner` metadata (`leader`, `manual-follower`, or API-level pending thread creation) plus cwd/thread-name metadata; string compatibility keys are derived internally and must not be the persisted source of ownership truth.
137
+ - Instance slot: extension-owned single-letter `A`-`Z` ordering metadata. New instances advance monotonically through the alphabet and wrap after `Z` only to a free slot; closed lower slots are not backfilled out of order, and live concurrent instances are capped to available alphabet slots rather than duplicating occupied letters. The compact `bot.lastSlot` cursor is durable across reloads/live-test history, so after it reaches `Z` a later new thread may intentionally become `A` again if `A` is currently free.
138
+ - Instance thread name: durable human-facing identity metadata that replaces slot-only thread titles. Fresh threads choose one baked 4-6 letter Latin-word name from the assigned slot's curated palette using provisioning timestamp entropy and create the Telegram thread with that title immediately. Telegram-originated prompt prefixes expose this thread identity label, never follower/leader roles or generic seeds. Bare slot letters are fallback/legacy labels only; agents are not asked to name or rename threads.
139
+ - Telegram destination: `TelegramTarget` as `{ chatId, threadId? }`, where `threadId` is Telegram `message_thread_id` for UI thread targets.
140
+
141
+ Guest-mode updates are owned by the current transport leader by default in Threaded Mode. Guest queries have no Telegram thread binding and no local follower identity, so the leader queues and answers them unless a future explicit guest-owner policy is added. Followers may still transport `answerGuestQuery` through the leader for replies to work if a guest turn is ever delegated deliberately, but implicit guest routing does not pick an arbitrary follower.
142
+
143
+ All inbound updates are gated by the configured authorized user id.
144
+
114
145
  ## Core Flows
115
146
 
116
147
  ### Inbound Turn Flow
@@ -147,7 +178,7 @@ Admission and planning validate lane contracts. Invalid lane/kind pairings fail
147
178
  Dispatch requires:
148
179
 
149
180
  - No active Telegram turn.
150
- - No pending Telegram dispatch already sent to π.
181
+ - No pending Telegram dispatch already sent to Pi.
151
182
  - No compaction in progress.
152
183
  - `ctx.isIdle()` is true.
153
184
  - `ctx.hasPendingMessages()` is false.
@@ -165,27 +196,28 @@ Immediate controls:
165
196
  - `/start` opens the main inline application menu.
166
197
  - `/model`, `/thinking`, `/queue`, and `/settings` are hidden shortcuts to menu sections.
167
198
  - `/compact` opens an inline confirmation dialog and then runs compaction when the bridge is idle.
168
- - `/next` dispatches the next queued turn, aborting π first when needed.
199
+ - `/next` dispatches the next queued turn, aborting Pi first when needed.
169
200
  - `/abort` aborts active work while preserving queued items. Abort-history preservation is enabled only for Telegram-owned active turns; later local/non-Telegram agent starts clear stale abort-history mode so the next Telegram prompt appends instead of absorbing old queued turns as history.
170
201
  - `/stop` aborts and clears waiting Telegram queue items.
171
202
 
172
203
  Queued controls:
173
204
 
174
205
  - `/continue` creates a priority Telegram-owned `continue` prompt.
175
- - Prompt-template commands expand Telegram-safe π template aliases before entering the prompt queue.
206
+ - Prompt-template commands expand Telegram-safe Pi template aliases before entering the prompt queue.
176
207
  - Model-switch continuation uses the control lane when an in-flight Telegram-owned run must be stopped and resumed.
177
208
 
178
209
  Queue and menu mutations are reachable through Telegram updates handled by the current polling owner. After ownership moves, the old instance keeps processing its accepted local queue, but it no longer receives new menu callbacks or control updates for remote mutation. UI label, navigation, tab, toggle, card, and dialog rules are defined in [UI Style](./ui-style.md). Callback prefix ownership is defined in [Callback Namespaces](./callback-namespaces.md).
179
210
 
180
211
  ### Compaction And Typing Status
181
212
 
182
- Manual `/compact` requires inline confirmation because accidental taps are disruptive. Auto-compaction and confirmed manual compaction both:
213
+ Manual `/compact` requires inline confirmation because accidental taps are disruptive. Confirmed manual compaction and auto-compaction both set the bridge compaction flag, block queued prompt dispatch, update status to `compacting`, and clear that state on compact completion, timeout fallback, or session shutdown.
214
+
215
+ Native typing during compaction is deliberately narrower than the compaction flag:
183
216
 
184
- - Set the bridge compaction flag.
185
- - Block queued prompt dispatch.
186
- - Update status to `compacting`.
187
- - Start Telegram native `typing` keepalive.
188
- - Stop typing on compact completion, timeout fallback, or session shutdown.
217
+ - Confirmed manual `/compact` always starts a native `typing` keepalive in the command target and stops it on completion/failure.
218
+ - Automatic/session compaction starts native `typing` only when there is an active Telegram-owned turn; it must reuse that active turn's target.
219
+ - Startup, reload, connect/reconnect, restore, leader/follower recovery, and idle/background compaction without an active Telegram turn must not send visible typing.
220
+ - Thread-targeted typing is sent to the concrete thread and mirrored to `All` as the aggregate activity surface.
189
221
 
190
222
  During active Telegram-owned turns, assistant message start/update hooks re-arm typing so transient provider/model errors do not leave a continuing run without Telegram activity feedback.
191
223
 
@@ -211,7 +243,7 @@ Final delivery attaches reply metadata only where requested. Reply parameters ap
211
243
 
212
244
  ### Outbound Artifacts And Assistant Actions
213
245
 
214
- Outbound files staged during an active Telegram turn are delivered after that turn completes. They use `telegram_attach`, are checked atomically per tool call, and use configurable size limits before photo/document upload. When no Telegram turn is active, `telegram_attach` sends files immediately to the paired/default chat or explicit `chat_id`; `telegram_message` provides direct local/TUI Markdown text delivery for explicit user requests and runs the same `telegram_button` markup planner so buttons attach to that text message. Direct local/TUI delivery is singleton-controlled: it requires this π instance to own `/telegram-connect`, while already accepted active-turn reply/attachment delivery remains session-local.
246
+ Outbound files staged during an active Telegram turn are delivered after that turn completes. They use `telegram_attach`, are checked atomically per tool call, and use configurable size limits before photo/document upload. When no Telegram turn is active, `telegram_attach` sends files immediately to the paired/default chat, an assigned follower thread, or an explicit `chat_id` plus optional `thread_id`; `telegram_message` provides direct local/TUI Markdown text delivery for explicit user requests and runs the same `telegram_button` markup planner so buttons attach to that text message. Direct local/TUI delivery is singleton-controlled: classic mode requires this Pi instance to own `/telegram-connect`, while Threaded Mode followers must be registered and route through the leader-owned transport. Already accepted active-turn reply/attachment delivery remains session-local.
215
247
 
216
248
  Assistant-authored final-message actions use hidden top-level comments:
217
249
 
@@ -251,14 +283,14 @@ Telegram prompt guidance is context-aware. Unconfigured sessions receive no brid
251
283
 
252
284
  ## In-Flight Model Switching
253
285
 
254
- When `/model` is used during an active Telegram-owned run, the bridge can emulate π's interactive stop/switch/continue workflow:
286
+ When `/model` is used during an active Telegram-owned run, the bridge can emulate Pi's interactive stop/switch/continue workflow:
255
287
 
256
288
  1. Apply the selected model immediately.
257
289
  2. Queue or stage a synthetic Telegram continuation turn.
258
290
  3. Abort the active Telegram turn immediately, or wait for the current tool to finish before aborting.
259
291
  4. Dispatch the continuation after abort completion.
260
292
 
261
- This is limited to Telegram-owned runs. If π is busy with non-Telegram work, the bridge refuses the switch instead of hijacking unrelated activity.
293
+ This is limited to Telegram-owned runs. If Pi is busy with non-Telegram work, the bridge refuses the switch instead of hijacking unrelated activity.
262
294
 
263
295
  ## Shutdown And Timer Lifecycle
264
296
 
@@ -266,7 +298,7 @@ This is limited to Telegram-owned runs. If π is busy with non-Telegram work, th
266
298
 
267
299
  Non-critical timers are `unref()`ed so print/headless processes are not kept alive only by Telegram housekeeping. This includes typing keepalive intervals, bounded typing-idle waits, deferred queue dispatch, media/text-group debounce windows, preview flush timers, and polling retry sleeps. Polling retry sleep is abort-aware, so shutdown does not wait for the normal retry delay after a polling error.
268
300
 
269
- Non-interactive `pi -p` runs must remain passive unless π provides a live Telegram session lifecycle. Loading the extension with `telegram.json`, proactive push settings, or existing lock state must not by itself keep the print-mode process alive or let a non-owner send proactive Telegram output.
301
+ Non-interactive `pi -p` runs must remain passive unless Pi provides a live Telegram session lifecycle. Loading the extension with `telegram.json`, proactive push settings, or existing lock state must not by itself keep the print-mode process alive or let a non-owner send proactive Telegram output.
270
302
 
271
303
  ## Related
272
304
 
@@ -27,7 +27,7 @@ myext:page:2
27
27
 
28
28
  ## pi-telegram fallback
29
29
 
30
- If `pi-telegram` receives callback data that is not owned by its built-in prefixes and no built-in handler consumes it, it forwards the click to π as:
30
+ If `pi-telegram` receives callback data that is not owned by its built-in prefixes and no built-in handler consumes it, it forwards the click to Pi as:
31
31
 
32
32
  ```text
33
33
  [callback] <callback_data>
package/docs/inbound.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Inbound Handlers
2
2
 
3
- `pi-telegram` can run ordered inbound handlers before a Telegram turn enters the π queue. Inbound handlers are the provider-neutral Telegram → π transformation bus for raw text and downloaded media/files. Extensions can also register programmatic inbound handlers with `registerTelegramInboundHandler()`, and voice extensions can register STT providers as a zero-config fallback for Telegram voice/audio files.
3
+ `pi-telegram` can run ordered inbound handlers before a Telegram turn enters the Pi queue. Inbound handlers are the provider-neutral Telegram → Pi transformation bus for raw text and downloaded media/files. Extensions can also register programmatic inbound handlers with `registerTelegramInboundHandler()`, and voice extensions can register STT providers as a zero-config fallback for Telegram voice/audio files.
4
4
 
5
5
  This document is the local inbound adaptation of the portable [Command Template Standard](./command-templates.md). It is also the canonical home for the legacy `attachmentHandlers` compatibility config.
6
6
 
package/docs/locks.md CHANGED
@@ -123,8 +123,6 @@ Avoid repeating the extension name in the body. Color is encouraged: extension t
123
123
 
124
124
  The previous owner may use `fs.watch`, mtime polling, or an existing status/timer tick. Long-lived watchers should compare against a snapshotted `pid`/`cwd` identity rather than a live pi context object, because session replacement such as `/new` makes captured contexts stale. The important contract is graceful singleton-runtime shutdown after ownership mismatch while session-local state that does not require polling remains owned by its original instance.
125
125
 
126
- For `pi-telegram`, direct local/TUI delivery tools (`telegram_message` and no-active-turn `telegram_attach`) and proactive local/headless final-result push are singleton-controlled and require current `/telegram-connect` ownership. They must fail or skip delivery when the lock is inactive or active elsewhere. Already accepted Telegram-turn reply delivery, previews, queued attachments, and queue finalization remain session-local and may complete after polling ownership moves away.
127
-
128
126
  ## Reset
129
127
 
130
128
  Delete `~/.pi/agent/locks.json` to reset singleton runtime ownership for all participating extensions without deleting their configuration files.