@llblab/pi-telegram 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -12
- package/BACKLOG.md +0 -10
- package/CHANGELOG.md +33 -2
- package/README.md +42 -25
- package/api/inbound.ts +14 -0
- package/api/keyboard.ts +10 -0
- package/api/outbound.ts +11 -0
- package/api/sections.ts +17 -0
- package/api/updates.ts +11 -0
- package/api/voice.ts +24 -0
- package/docs/README.md +7 -5
- package/docs/architecture.md +176 -135
- package/docs/callback-namespaces.md +3 -3
- package/docs/command-templates.md +81 -24
- package/docs/{inbound-handlers.md → inbound.md} +13 -10
- package/docs/locks.md +3 -3
- package/docs/{outbound-handlers.md → outbound.md} +13 -10
- package/docs/public-api.md +266 -0
- package/docs/{extension-sections.md → sections.md} +31 -27
- package/docs/ui-style.md +165 -0
- package/docs/{external-handlers.md → updates.md} +33 -31
- package/docs/voice.md +17 -14
- package/index.ts +86 -261
- package/lib/bindings.ts +301 -0
- package/lib/command-templates.ts +163 -32
- package/lib/commands.ts +114 -1
- package/lib/config.ts +45 -4
- package/lib/{inbound-handlers.ts → inbound.ts} +5 -4
- package/lib/lifecycle.ts +122 -1
- package/lib/menu-model.ts +3 -3
- package/lib/menu-queue.ts +1 -1
- package/lib/menu-settings.ts +63 -32
- package/lib/menu-status.ts +1 -1
- package/lib/menu.ts +1 -1
- package/lib/{outbound-handlers.ts → outbound.ts} +21 -11
- package/lib/pi.ts +4 -0
- package/lib/polling.ts +4 -3
- package/lib/preview.ts +1 -1
- package/lib/routing.ts +45 -13
- package/lib/{extension-sections.ts → sections.ts} +37 -8
- package/lib/time-injection.ts +1 -1
- package/lib/updates.ts +121 -1
- package/lib/voice.ts +33 -14
- package/package.json +11 -1
- package/lib/external-handlers.ts +0 -166
package/docs/architecture.md
CHANGED
|
@@ -1,198 +1,239 @@
|
|
|
1
1
|
# Telegram Bridge Architecture
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Purpose
|
|
4
4
|
|
|
5
|
-
`pi-telegram` is a session-local π extension that binds one Telegram DM to one running π session.
|
|
5
|
+
`pi-telegram` is a session-local π extension that binds one Telegram DM to one running π session. It owns the Telegram bridge boundary:
|
|
6
6
|
|
|
7
|
-
- Poll Telegram updates and enforce single-user pairing
|
|
8
|
-
- Translate Telegram
|
|
9
|
-
- Stream and deliver π responses back to Telegram
|
|
10
|
-
-
|
|
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.
|
|
10
|
+
- Provide Telegram-native controls for queueing, model/thinking/settings menus, compaction, abort/stop, prompt templates, reactions, and outbound artifacts.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
This document is the architectural map. Focused behavior standards live in sibling docs:
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- [Public API](./public-api.md) — stable commands, config, package entrypoints, assistant markup, extension APIs, and compatibility boundaries.
|
|
15
|
+
- [UI Style](./ui-style.md) — inline UI labels, navigation, state markers, cards, and dialogs.
|
|
16
|
+
- [Callback Namespaces](./callback-namespaces.md) — callback prefix ownership and fallback rules.
|
|
17
|
+
- [Sections](./sections.md) — structured Telegram menu sections.
|
|
18
|
+
- [Updates](./updates.md) — update classification, default-routing plans, and raw Telegram update interception.
|
|
19
|
+
- [Voice Integration](./voice.md) — voice reply policy and STT/TTS provider surface.
|
|
20
|
+
- [Command Templates](./command-templates.md) — shell-free command-template contract.
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
## Runtime Topology
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
`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.
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
The repository uses a **Flat Domain DAG**:
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
- Local imports must form a directed acyclic graph.
|
|
29
|
+
- Cohesive domain files are preferred over atomizing every helper.
|
|
30
|
+
- Shared buckets such as `lib/constants.ts` or `lib/types.ts` are avoided.
|
|
31
|
+
- Constants and state types live with their owning domain.
|
|
32
|
+
- Narrow structural projections are allowed when they avoid importing broader runtime or wire DTOs.
|
|
33
|
+
- Source file headers include `Zones:` tags so cross-cutting responsibility stays visible without folder nesting.
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
### Domain Ownership Map
|
|
25
36
|
|
|
26
|
-
- `index.ts`:
|
|
27
|
-
- `api`: Bot API
|
|
28
|
-
- `config` / `setup`:
|
|
29
|
-
- `locks` / `polling`: singleton
|
|
30
|
-
- `updates` / `routing`: update classification
|
|
31
|
-
- `media` / `text-groups` / `time-injection` / `turns` / `inbound
|
|
32
|
-
- `queue`: queue item contracts, lane admission/order,
|
|
33
|
-
- `runtime`: session-local coordination primitives: counters,
|
|
34
|
-
- `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-
|
|
35
|
-
-
|
|
36
|
-
- `keyboard`: shared
|
|
37
|
-
- `preview` / `replies` / `rendering`: preview lifecycle
|
|
38
|
-
- `outbound
|
|
39
|
-
- `outbound-attachments`: `telegram_attach
|
|
40
|
-
- `status`: status
|
|
41
|
-
- `lifecycle` / `prompts` / `prompt-templates` / `pi`: π hook registration, Telegram
|
|
42
|
-
- `command-templates`:
|
|
37
|
+
- `index.ts`: composition root for live ports, session state, transport adapters, and lifecycle registration.
|
|
38
|
+
- `api`: Bot API helpers, retries, uploads/downloads, temp cleanup, byte limits, chat actions, lazy token clients, and API error recording.
|
|
39
|
+
- `config` / `setup`: `telegram.json`, bot token setup, first-user pairing, authorization, env fallback, atomic persistence, and live config accessors.
|
|
40
|
+
- `locks` / `polling`: singleton polling ownership, takeover/restart behavior, long-poll controller state, offset persistence, and poll-loop wiring.
|
|
41
|
+
- `updates` / `routing`: update classification, authorization planning, callbacks, edited messages, reactions, and inbound route composition.
|
|
42
|
+
- `media` / `text-groups` / `time-injection` / `turns` / `inbound`: inbound text/media/file extraction, media-group debounce, long-text coalescing, optional `[time]` context, handler execution, and prompt-turn assembly/editing.
|
|
43
|
+
- `queue`: queue item contracts, lane admission/order, readiness gates, mutations, dispatch runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
|
|
44
|
+
- `runtime`: session-local coordination primitives: counters, flags, setup guard, abort handler, typing timers, dispatch flags, and reset binding.
|
|
45
|
+
- `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu-queue` / `menu-settings` / `menu` / `commands`: model identity, thinking levels, scoped model handling, menu render/callback behavior, slash commands, bot commands, and interactive controls.
|
|
46
|
+
- `sections`: Telegram menu-section registry, opaque section callback tokens, render/callback dispatch, safe section ports, and diagnostics.
|
|
47
|
+
- `keyboard`: shared inline-keyboard reply-markup shape only; feature domains own labels, callback data, and behavior.
|
|
48
|
+
- `preview` / `replies` / `rendering`: streaming preview lifecycle, final reply delivery, reply parameters, Telegram HTML rendering, chunking, and stable preview snapshots.
|
|
49
|
+
- `outbound`: outbound text transformations, assistant-authored action comments, voice/button artifacts, and generated callback actions.
|
|
50
|
+
- `outbound-attachments`: `telegram_attach`, queued outbound files, stat/limit checks, and photo/document delivery classification.
|
|
51
|
+
- `status`: status bar/status-message rendering, queue-lane summaries, redacted event ring, and grouped diagnostics.
|
|
52
|
+
- `lifecycle` / `prompts` / `prompt-templates` / `pi`: π hook registration, Telegram prompt guidance, prompt-template discovery/expansion, and centralized direct π SDK imports.
|
|
53
|
+
- `command-templates`: shell-free command-template helpers, composition expansion, placeholder substitution, executable resolution, warnings, and retry/timeout semantics.
|
|
43
54
|
|
|
44
|
-
|
|
55
|
+
### Guarded Invariants
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
- Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`; future external section labels, callbacks, and dispatch stay in `extension-sections`; core queue mechanics stay in `queue`
|
|
48
|
-
- Domain helpers use narrow structural projections when that avoids importing concrete wire DTOs or broader runtime objects unnecessarily
|
|
49
|
-
- Preview appearance stays in `rendering`; preview transport/lifecycle stays in `preview`
|
|
50
|
-
- Direct `node:*` file-operation imports stay in owning domains, not in `index.ts`
|
|
51
|
-
- `index.ts` uses namespace imports for local bridge domains so orchestration reads as `Queue.*`, `Turns.*`, and `Rendering.*`
|
|
52
|
-
- Architecture-invariant tests guard the acyclic import graph, pi SDK centralization, entrypoint purity, runtime-domain isolation, structural leaf-domain isolation, menu/model boundaries, API/config separation, media/update/API separation, and outbound-attachment boundary isolation
|
|
53
|
-
- Mirrored domain regression coverage lives in `/tests/*.test.ts`; test helpers stay local to the mirrored suite by default, and shared fixture folders are justified only by reuse across multiple domain suites
|
|
57
|
+
Architecture invariant tests protect:
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
- Acyclic local imports.
|
|
60
|
+
- Direct π SDK imports centralized in the `pi` adapter.
|
|
61
|
+
- `index.ts` as a composition root without local runtime adapter logic.
|
|
62
|
+
- Runtime state isolation from local domain imports.
|
|
63
|
+
- Structural leaf-domain isolation.
|
|
64
|
+
- Menu/model boundary direction.
|
|
65
|
+
- API/config separation.
|
|
66
|
+
- Media/update/API decoupling.
|
|
67
|
+
- Outbound attachment isolation from queue, inbound media, and API helpers.
|
|
56
68
|
|
|
57
|
-
`/
|
|
69
|
+
Mirrored domain regressions live in `/tests/*.test.ts`. Shared test fixtures should exist only when multiple suites genuinely reuse them.
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
2. Otherwise use the first configured environment variable from the supported Telegram token list
|
|
61
|
-
3. Fall back to the example placeholder when no real value exists
|
|
71
|
+
## Configuration And Ownership
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
Telegram configuration lives in `~/.pi/agent/telegram.json`. Polling ownership lives separately in `~/.pi/agent/locks.json` under `@llblab/pi-telegram`.
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
### Setup Flow
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
`/telegram-setup` progressively resolves the bot token:
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
1. Use the locally saved token when present.
|
|
80
|
+
2. Otherwise use the first supported Telegram token environment variable.
|
|
81
|
+
3. Otherwise show the example placeholder.
|
|
70
82
|
|
|
71
|
-
|
|
83
|
+
`ctx.ui.input()` only supports placeholder text, so setup uses `ctx.ui.editor()` when a real default must appear already filled in. Persisted config is written through a private temp file plus atomic rename and left with `0600` permissions.
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
2. Each update offset is persisted only after the update handler succeeds; repeated handler failures are bounded so one poisoned update cannot stall polling forever
|
|
75
|
-
3. The bridge filters to the paired private user
|
|
76
|
-
4. Media groups are coalesced into a single Telegram turn when needed
|
|
77
|
-
5. Slash command parsing uses only the new message text/caption, while Telegram `reply_to_message` text/caption is injected later as prompt-only `[reply]` context for normal queued turns
|
|
78
|
-
6. Files are streamed into `~/.pi/agent/tmp/telegram` with a default 50 MiB size limit, partial-download cleanup on failures, and stale temp cleanup on session start; operators can tune the limit with `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES`
|
|
79
|
-
7. Configured inbound handlers may run on raw text or downloaded files by MIME wildcard, Telegram attachment type, or generic match selector; command templates receive safe command-arg substitution for `{text}`, `{file}`, `{mime}`, and `{type}` where applicable
|
|
80
|
-
8. Matching media/file handlers are tried in config order: a non-zero exit records diagnostics and falls back to the next matching handler, while the first successful handler stops the chain
|
|
81
|
-
9. Local attachments stay visible under `[attachments] <directory>` with relative file entries, and handler stdout is appended under `[outputs]` before the agent sees the turn; failed handlers omit output while keeping the attachment entry
|
|
82
|
-
10. Optional `time` config may add a compact final `[time]` prompt line after attachment/output/voice sections, either every turn or per-chat after the configured millisecond interval, using the system timezone
|
|
83
|
-
11. A `PendingTelegramTurn` is created and queued locally
|
|
84
|
-
12. Telegram `edited_message` updates are routed separately and update a matching queued turn when the original message has not been dispatched yet
|
|
85
|
-
13. The queue dispatcher sends the turn into π only when dispatch is safe
|
|
85
|
+
### Runtime Ownership
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
- `/telegram-connect` acquires or moves singleton ownership before polling starts.
|
|
88
|
+
- `/telegram-disconnect` stops polling and releases ownership.
|
|
89
|
+
- 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.
|
|
90
|
+
- Session replacement suspends polling/watchers without releasing ownership so the next session-start hook in the same process can resume.
|
|
91
|
+
- Live polling owners require explicit takeover confirmation.
|
|
92
|
+
- Long-lived timers use snapshotted ownership context and stop local polling when the lock no longer points at their own process.
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
## Core Flows
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
- `queueLane`: control vs priority vs default
|
|
98
|
+
### Inbound Turn Flow
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
1. Poll updates through `getUpdates`.
|
|
101
|
+
2. Persist update offsets only after successful handling; repeated handler failures are bounded.
|
|
102
|
+
3. Filter to the paired private user.
|
|
103
|
+
4. Dispatch owned callbacks and controls before fallback prompt forwarding.
|
|
104
|
+
5. Coalesce media groups and likely split long text when needed.
|
|
105
|
+
6. Download files into `~/.pi/agent/tmp/telegram` with size limits and partial-download cleanup.
|
|
106
|
+
7. Run configured/programmatic inbound handlers in order, appending successful stdout under `[outputs]`.
|
|
107
|
+
8. Add local attachments under `[attachments]`, optional voice context, and optional final `[time]` context.
|
|
108
|
+
9. Build a `PendingTelegramTurn` and append it to the bridge queue.
|
|
109
|
+
10. Handle `edited_message` updates separately while the original turn is still queued.
|
|
110
|
+
11. Dispatch only when all safety gates are clear.
|
|
97
111
|
|
|
98
|
-
-
|
|
99
|
-
- Queued prompt command: `/continue` enqueues a priority Telegram-owned `continue` prompt. Prompt-template commands such as `/template_name args` expand the matching π template before entering the normal prompt queue. Dispatch rank: priority for `/continue`, otherwise default.
|
|
100
|
-
- Control queue: model-switch continuation turns and future deferred controls use `queueLane: control`, accept control items and continuation prompts, and dispatch at rank `0`.
|
|
101
|
-
- Priority prompt queue: a waiting prompt promoted by `👍`, `⚡️`, `❤️`, `🕊`, or `🔥` uses `kind: prompt`, `queueLane: priority`, and dispatches at rank `1`.
|
|
102
|
-
- Default prompt queue: normal Telegram text/media turns use `kind: prompt`, `queueLane: default`, and dispatch at rank `2`.
|
|
112
|
+
Long-text split recovery is intentionally conservative: only human text at or above the near-limit threshold opens the debounce window; commands, bots, captions, media groups, and normal short follow-ups bypass it.
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
### Queue And Dispatch Safety
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
The bridge keeps its own Telegram queue. Queue items have two explicit dimensions:
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
- `kind`: `prompt` or `control`.
|
|
119
|
+
- `queueLane`: `control`, `priority`, or `default`.
|
|
109
120
|
|
|
110
|
-
|
|
111
|
-
- No pending Telegram dispatch already sent to π
|
|
112
|
-
- No compaction in progress
|
|
113
|
-
- `ctx.isIdle()` being true
|
|
114
|
-
- `ctx.hasPendingMessages()` being false
|
|
121
|
+
Dispatch rank:
|
|
115
122
|
|
|
116
|
-
|
|
123
|
+
1. `control` lane.
|
|
124
|
+
2. `priority` prompt lane.
|
|
125
|
+
3. `default` prompt lane.
|
|
117
126
|
|
|
118
|
-
|
|
127
|
+
Admission and planning validate lane contracts. Invalid lane/kind pairings fail predictably instead of being silently coerced.
|
|
119
128
|
|
|
120
|
-
|
|
129
|
+
Dispatch requires:
|
|
121
130
|
|
|
122
|
-
|
|
131
|
+
- No active Telegram turn.
|
|
132
|
+
- No pending Telegram dispatch already sent to π.
|
|
133
|
+
- No compaction in progress.
|
|
134
|
+
- `ctx.isIdle()` is true.
|
|
135
|
+
- `ctx.hasPendingMessages()` is false.
|
|
123
136
|
|
|
124
|
-
|
|
137
|
+
A dispatched prompt remains queued until `agent_start` consumes it. This keeps the active Telegram turn bound for previews, attachments, aborts, and final replies.
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
Post-agent-end queue dispatch uses a session-bound deferred dispatcher. It is activated on session start, clears timers on shutdown, and skips callbacks from older generations before touching `ExtensionContext`.
|
|
127
140
|
|
|
128
|
-
|
|
141
|
+
### Controls And Menus
|
|
129
142
|
|
|
130
|
-
|
|
131
|
-
- Real code blocks must remain literal and escaped
|
|
132
|
-
- Supported absolute HTTP(S) and mailto links should stay clickable, with generated HTML attributes escaped separately from text content, while unsupported link forms such as unresolved references, footnotes, or relative links without a known base should degrade safely instead of producing broken Telegram anchors
|
|
133
|
-
- Markdown tables should keep their internal separators but drop the outer left and right borders when rendered as monospace blocks so narrow Telegram clients keep more usable width; table padding should count grapheme/display width for multi-codepoint emoji, combining marks, and wide Unicode where possible, and the Telegram before-agent prompt suffix also asks the assistant to prefer narrow table columns because many chats are read on phone-width screens
|
|
134
|
-
- Unordered Markdown lists should render with a monospace `-` marker and ordered Markdown lists should render with monospace numeric markers so list indentation stays more predictable on narrow Telegram clients
|
|
135
|
-
- Real Markdown task-list items should render with checkbox markers, while standalone `[x]` and `[ ]` prose should stay literal instead of being reinterpreted as checklists
|
|
136
|
-
- Nested Markdown quotes should flatten into one Telegram blockquote with added non-breaking-space indentation because Telegram does not render nested blockquotes reliably
|
|
137
|
-
- Original blank-line spacing between Markdown blocks should stay intact in both preview and final rendering instead of being collapsed to one generic block separator, while headings should still keep readable separation from following blocks such as code fences even when source Markdown omits a blank line
|
|
138
|
-
- Long replies, including raw HTML-mode replies used by interactive/status flows, must be split below Telegram's 4096-character limit
|
|
139
|
-
- Raw HTML chunking lives with the rendering helpers in `/lib/rendering.ts` and should preserve/reopen active tags across chunk boundaries where possible
|
|
140
|
-
- Preview rendering uses stable top-level Markdown blocks for rich Telegram HTML and appends the still-growing tail conservatively as readable plain text so the preview stays valid even when the answer is incomplete
|
|
143
|
+
Telegram controls execute through command/callback domains, not by entering the normal prompt queue unless they intentionally create a prompt turn.
|
|
141
144
|
|
|
142
|
-
|
|
145
|
+
Immediate controls:
|
|
143
146
|
|
|
144
|
-
|
|
147
|
+
- `/start` opens the main inline application menu.
|
|
148
|
+
- `/model`, `/thinking`, `/queue`, and `/settings` are hidden shortcuts to menu sections.
|
|
149
|
+
- `/compact` opens an inline confirmation dialog and then runs compaction when the bridge is idle.
|
|
150
|
+
- `/next` dispatches the next queued turn, aborting π first when needed.
|
|
151
|
+
- `/abort` aborts the active Telegram-owned run while preserving queued items.
|
|
152
|
+
- `/stop` aborts and clears waiting Telegram queue items.
|
|
145
153
|
|
|
146
|
-
|
|
154
|
+
Queued controls:
|
|
147
155
|
|
|
148
|
-
|
|
156
|
+
- `/continue` creates a priority Telegram-owned `continue` prompt.
|
|
157
|
+
- Prompt-template commands expand Telegram-safe π template aliases before entering the prompt queue.
|
|
158
|
+
- Model-switch continuation uses the control lane when an in-flight Telegram-owned run must be stopped and resumed.
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
2. Send or update that preview through `sendMessage` plus `editMessageText`, because `sendMessageDraft` is text-only for rich previews
|
|
152
|
-
3. Serialize overlapping preview flushes so older Telegram edit calls cannot race newer streamed snapshots
|
|
153
|
-
4. Replace the preview with the final rendered reply when generation ends
|
|
160
|
+
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).
|
|
154
161
|
|
|
155
|
-
|
|
162
|
+
### Compaction And Typing Status
|
|
156
163
|
|
|
157
|
-
|
|
164
|
+
Manual `/compact` requires inline confirmation because accidental taps are disruptive. Auto-compaction and confirmed manual compaction both:
|
|
158
165
|
|
|
159
|
-
|
|
166
|
+
- Set the bridge compaction flag.
|
|
167
|
+
- Block queued prompt dispatch.
|
|
168
|
+
- Update status to `compacting`.
|
|
169
|
+
- Start Telegram native `typing` keepalive.
|
|
170
|
+
- Stop typing on compact completion, timeout fallback, or session shutdown.
|
|
160
171
|
|
|
161
|
-
|
|
172
|
+
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.
|
|
162
173
|
|
|
163
|
-
|
|
174
|
+
### Rendering And Delivery
|
|
164
175
|
|
|
165
|
-
|
|
176
|
+
Telegram replies are rendered as Telegram HTML, not raw Markdown. The renderer is Telegram-specific and regression-prone.
|
|
166
177
|
|
|
167
|
-
|
|
178
|
+
Key guarantees:
|
|
168
179
|
|
|
169
|
-
|
|
180
|
+
- Real code blocks stay literal and escaped.
|
|
181
|
+
- Supported absolute links stay clickable; unsupported links degrade safely.
|
|
182
|
+
- Markdown tables render as compact monospace blocks and count grapheme/display width.
|
|
183
|
+
- Lists, task lists, quotes, headings, and blank-line spacing have Telegram-specific preservation rules.
|
|
184
|
+
- Long replies are chunked below Telegram limits with balanced HTML where possible.
|
|
185
|
+
- Streaming previews prefer stable rich blocks and append the unstable tail conservatively as readable plain text.
|
|
186
|
+
- Preview flushes are serialized so older edits cannot race newer snapshots.
|
|
170
187
|
|
|
171
|
-
|
|
172
|
-
- Inline application-menu buttons for model, thinking, and queue controls, applying idle selections immediately while still respecting busy-run restart rules; model-menu inputs are cached briefly and stored inline-menu states are pruned by TTL/LRU so old keyboards expire predictably
|
|
173
|
-
- Hidden `/model` and `/thinking` shortcuts for opening the model and thinking sections directly while keeping settings out of the visible bot command menu
|
|
174
|
-
- `/compact` for Telegram-triggered π session compaction when the bridge is idle
|
|
175
|
-
- `/queue` for opening the queue section of the inline application menu; the same section is reachable from the status/main menu and supports top-anchored Back navigation, Priority/Normal tabs, and cancellation
|
|
176
|
-
- `/next` for dispatching the next queued turn, aborting the active run first when π is busy
|
|
177
|
-
- `/continue` for enqueueing a Telegram-owned `continue` prompt, without aborting the current turn or forcing the next queued item
|
|
178
|
-
- `/abort` for aborting the active Telegram-owned run while preserving queued items for manual continuation
|
|
179
|
-
- `/stop` for aborting the active Telegram-owned run and clearing waiting Telegram queue items
|
|
180
|
-
- `/telegram-status` for π-side diagnostics as grouped line-by-line sections separated by blank lines: connection, polling, execution, queue, and the recent redacted runtime/API event ring. These sections include polling state, last update id, active turn source ids, pending dispatch, compaction state, active tool count, pending model-switch state, total queue depth, and queue-lane counts. The event ring records transport/API, polling/update, prompt-dispatch, control-action, typing, compaction, setup, session-lifecycle, and attachment queue/delivery failures; benign unchanged edit responses and unsupported empty draft-clear attempts are filtered out so expected preview transport noise does not obscure real failures
|
|
181
|
-
- `/telegram-settings` for π-side bridge settings; it currently exposes proactive push as a local toggle backed by the same `telegram.json` flag as the hidden Telegram `/settings` menu
|
|
182
|
-
- Queue reactions apply to waiting text, voice, file, image, and media-group turns by matching the turn's source Telegram message ids: `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting prompts, while `👎`, `👻`, `💔`, `💩`, and `🗑` remove waiting turns because ordinary Telegram DM message deletions are not exposed through the Bot API polling path this bridge uses
|
|
188
|
+
Final delivery attaches reply metadata only where requested. Reply parameters apply only to the first chunk of split messages; continuation chunks are adjacent normal messages. Media-group turns reply to the representative message id.
|
|
183
189
|
|
|
184
|
-
|
|
190
|
+
### Outbound Artifacts And Assistant Actions
|
|
191
|
+
|
|
192
|
+
Outbound files are delivered after the active Telegram turn completes. They must be staged with `telegram_attach`, are checked atomically per tool call, and use configurable size limits before photo/document upload.
|
|
193
|
+
|
|
194
|
+
Assistant-authored final-message actions use hidden top-level comments:
|
|
195
|
+
|
|
196
|
+
- `telegram_voice` creates voice reply artifacts through configured outbound handlers, programmatic voice handlers, or registered synthesis providers.
|
|
197
|
+
- `telegram_button` creates inline buttons whose callbacks enqueue the configured prompt text as a normal Telegram prompt turn.
|
|
198
|
+
|
|
199
|
+
Preview rendering strips top-level action comments while streaming. Comments inside code fences, quotes, lists, or indented examples stay literal.
|
|
200
|
+
|
|
201
|
+
Unknown callback data outside owned prefixes is forwarded as `[callback] <data>` only after built-in and extension handlers decline it.
|
|
202
|
+
|
|
203
|
+
## Extension Surfaces
|
|
204
|
+
|
|
205
|
+
`pi-telegram` intentionally owns one `getUpdates` loop per bot. `polling` owns that internal loop; `updates` owns classification/default-routing plans plus the public handler registry layered extensions use to observe or consume updates without opening a competing polling connection. Layered extensions should integrate through extension surfaces instead of polling the same bot independently.
|
|
206
|
+
|
|
207
|
+
- Raw update observation/consumption: [Updates](./updates.md).
|
|
208
|
+
- Structured inline UI sections: [Sections](./sections.md).
|
|
209
|
+
- Callback namespace discipline: [Callback Namespaces](./callback-namespaces.md).
|
|
210
|
+
- Voice/STT/TTS providers: [Voice Integration](./voice.md).
|
|
211
|
+
- Inbound/outbound command-template handlers: [Command Templates](./command-templates.md).
|
|
185
212
|
|
|
186
|
-
|
|
213
|
+
Extension callbacks must avoid `pi-telegram` owned prefixes such as `compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, and `section:`.
|
|
214
|
+
|
|
215
|
+
## Diagnostics And Operational Behavior
|
|
216
|
+
|
|
217
|
+
Status rendering distinguishes connected, active, dispatching, queued, tool-running, model-switching, and compacting states. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, status remains active instead of degrading to connected.
|
|
218
|
+
|
|
219
|
+
Queue reactions are shortcut controls for waiting turns. Promotion reactions (`👍`, `⚡️`, `❤️`, `🕊`, `🔥`) move prompts to priority; removal reactions (`👎`, `👻`, `💔`, `💩`, `🗑`) remove waiting turns because ordinary Telegram DM deletions are not exposed through Bot API polling.
|
|
220
|
+
|
|
221
|
+
`/telegram-status` records grouped diagnostics for transport/API, polling/update, prompt dispatch, controls, typing, compaction, setup, session lifecycle, attachment queue/delivery, and recent redacted runtime events. Expected preview noise such as unchanged edit responses is filtered out.
|
|
222
|
+
|
|
223
|
+
When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not mirrored because the bot does not own terminal user messages.
|
|
224
|
+
|
|
225
|
+
Telegram prompt guidance asks assistants to keep dense mobile-visible text around 37 display cells where possible, because emoji and wide Unicode make raw character counts misleading.
|
|
226
|
+
|
|
227
|
+
## In-Flight Model Switching
|
|
187
228
|
|
|
188
|
-
|
|
229
|
+
When `/model` is used during an active Telegram-owned run, the bridge can emulate π's interactive stop/switch/continue workflow:
|
|
189
230
|
|
|
190
|
-
1.
|
|
191
|
-
2.
|
|
192
|
-
3.
|
|
193
|
-
4.
|
|
231
|
+
1. Apply the selected model immediately.
|
|
232
|
+
2. Queue or stage a synthetic Telegram continuation turn.
|
|
233
|
+
3. Abort the active Telegram turn immediately, or wait for the current tool to finish before aborting.
|
|
234
|
+
4. Dispatch the continuation after abort completion.
|
|
194
235
|
|
|
195
|
-
This
|
|
236
|
+
This is limited to Telegram-owned runs. If π is busy with non-Telegram work, the bridge refuses the switch instead of hijacking unrelated activity.
|
|
196
237
|
|
|
197
238
|
## Related
|
|
198
239
|
|
|
@@ -20,7 +20,7 @@ myext:page:2
|
|
|
20
20
|
|
|
21
21
|
- Use a stable extension-owned namespace, preferably the package or extension name without scope punctuation.
|
|
22
22
|
- Keep the namespace lowercase ASCII: `a-z`, `0-9`, `_`, `-`.
|
|
23
|
-
- Do not use `pi-telegram` owned prefixes: `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, `section:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI. `section:` is owned by the Extension Sections platform (0.10.0+), documented in [Extension Sections](./
|
|
23
|
+
- Do not use `pi-telegram` owned prefixes: `compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, `section:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI. `compact:` is owned by the manual compaction confirmation dialog. `section:` is owned by the Extension Sections platform (0.10.0+), documented in [Extension Sections](./sections.md). `settings:` is owned for the built-in Settings submenu.
|
|
24
24
|
- Keep the full `callback_data` within Telegram's 64-byte limit.
|
|
25
25
|
- Put only opaque ids or small enum values in payloads; do not store secrets, full prompts, or large state.
|
|
26
26
|
- Treat callbacks as untrusted input. Validate namespace, action, and payload before executing side effects.
|
|
@@ -37,7 +37,7 @@ Layered extensions may intercept that message and handle their own namespace. If
|
|
|
37
37
|
|
|
38
38
|
## Extension sections
|
|
39
39
|
|
|
40
|
-
[Telegram Extension Sections](./
|
|
40
|
+
[Telegram Extension Sections](./sections.md) are a higher-level UI contract over this namespace rule. A section owns a canonical extension identity such as `@llblab/pi-telegram-explorer`, but its Telegram `callback_data` should use the `pi-telegram` owned `section:` prefix plus a compact token, because Telegram limits callback payloads to 64 bytes.
|
|
41
41
|
|
|
42
42
|
Conceptual form:
|
|
43
43
|
|
|
@@ -45,4 +45,4 @@ Conceptual form:
|
|
|
45
45
|
section:<token>:<action>[:<payload>]
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
The token maps back to the full section identity inside the section registry. Section authors should not hand-roll `section:` callbacks outside the section context helpers, and ordinary layered extensions should continue using their own namespace plus
|
|
48
|
+
The token maps back to the full section identity inside the section registry. Section authors should not hand-roll `section:` callbacks outside the section context helpers, and ordinary layered extensions should continue using their own namespace plus update handlers or the `[callback]` fallback.
|