@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.
- package/AGENTS.md +67 -32
- package/BACKLOG.md +59 -19
- package/CHANGELOG.md +42 -15
- package/README.md +63 -35
- package/docs/README.md +3 -1
- package/docs/architecture.md +55 -23
- package/docs/callback-namespaces.md +1 -1
- package/docs/inbound.md +1 -1
- package/docs/locks.md +0 -2
- package/docs/multi-instance-bus.md +410 -0
- package/docs/outbound.md +4 -3
- package/docs/public-api.md +12 -10
- package/docs/sections.md +2 -2
- package/docs/ui-style.md +76 -0
- package/index.ts +789 -32
- package/lib/bindings.ts +68 -12
- package/lib/bus-api.ts +314 -0
- package/lib/bus-follower.ts +853 -0
- package/lib/bus-leader.ts +915 -0
- package/lib/bus.ts +866 -0
- package/lib/command-templates.ts +9 -11
- package/lib/commands.ts +133 -47
- package/lib/config.ts +53 -5
- package/lib/lifecycle.ts +23 -7
- package/lib/locks.ts +230 -66
- package/lib/media.ts +30 -2
- package/lib/menu-model.ts +48 -17
- package/lib/menu-queue.ts +51 -20
- package/lib/menu-settings.ts +9 -5
- package/lib/menu-status.ts +3 -0
- package/lib/menu-thinking.ts +3 -0
- package/lib/menu.ts +67 -26
- package/lib/outbound-attachments.ts +102 -17
- package/lib/outbound-buttons.ts +6 -2
- package/lib/outbound-voice.ts +31 -11
- package/lib/outbound.ts +6 -4
- package/lib/ownership.ts +119 -0
- package/lib/pi.ts +26 -3
- package/lib/polling.ts +477 -7
- package/lib/preview.ts +141 -88
- package/lib/prompt-templates.ts +3 -3
- package/lib/prompts.ts +80 -30
- package/lib/queue.ts +193 -91
- package/lib/rendering.ts +0 -25
- package/lib/replies.ts +187 -55
- package/lib/routing.ts +1673 -9
- package/lib/runtime-log.ts +123 -0
- package/lib/runtime.ts +84 -12
- package/lib/sections.ts +28 -21
- package/lib/setup.ts +9 -2
- package/lib/status.ts +532 -9
- package/lib/sync.ts +618 -0
- package/lib/target.ts +49 -0
- package/lib/telegram-api.ts +409 -41
- package/lib/text-groups.ts +5 -1
- package/lib/thread-reconciler.ts +915 -0
- package/lib/threads.ts +2205 -0
- package/lib/turns.ts +48 -3
- package/lib/updates.ts +355 -32
- package/package.json +24 -2
- package/docs/telegram-bot-api-rich-messages.md +0 -890
package/README.md
CHANGED
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
82
|
-
- Open `/start` and get a Telegram control panel for the running
|
|
83
|
-
- Fire off three tasks while
|
|
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;
|
|
87
|
-
- Drop a screenshot and ask, "what is broken here?" The image payload reaches
|
|
88
|
-
- Ask for a generated file; when
|
|
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
|
|
96
|
-
- **`/next`**: Dispatch the next queued turn, aborting
|
|
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
|
|
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
|
-
###
|
|
118
|
+
### Pi commands
|
|
106
119
|
|
|
107
|
-
Run these inside
|
|
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
|
|
111
|
-
- **`/telegram-disconnect`**: Stop polling in the current
|
|
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
|
|
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
|
|
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
|
|
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 →
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 →
|
|
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
|
|
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
|
package/docs/architecture.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`pi-telegram` is a session-local
|
|
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
|
|
9
|
-
- Stream previews and deliver final
|
|
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
|
|
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
|
|
54
|
-
- `
|
|
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`:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|